From ea7ddd1c85f663d602290a95d56f1a71b851f112 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Thu, 28 Apr 2011 16:03:35 +0100 Subject: [PATCH] NanoPHP is now only using GIT!! --- LICENCE | 20 + README | 100 + cli | 16 + lib/Swift/CHANGES | 61 + lib/Swift/LICENSE | 165 + lib/Swift/README | 30 + lib/Swift/VERSION | 1 + lib/Swift/lib/classes/Swift.php | 57 + lib/Swift/lib/classes/Swift/Attachment.php | 75 + .../AbstractFilterableInputStream.php | 178 + .../Swift/ByteStream/ArrayByteStream.php | 190 + .../Swift/ByteStream/FileByteStream.php | 177 + .../lib/classes/Swift/CharacterReader.php | 60 + .../GenericFixedWidthReader.php | 96 + .../Swift/CharacterReader/UsAsciiReader.php | 83 + .../Swift/CharacterReader/Utf8Reader.php | 183 + .../classes/Swift/CharacterReaderFactory.php | 29 + .../SimpleCharacterReaderFactory.php | 119 + .../lib/classes/Swift/CharacterStream.php | 86 + .../CharacterStream/ArrayCharacterStream.php | 319 + .../CharacterStream/NgCharacterStream.php | 300 + .../lib/classes/Swift/DependencyContainer.php | 349 + .../lib/classes/Swift/DependencyException.php | 30 + lib/Swift/lib/classes/Swift/EmbeddedFile.php | 73 + lib/Swift/lib/classes/Swift/Encoder.php | 32 + .../classes/Swift/Encoder/Base64Encoder.php | 63 + .../lib/classes/Swift/Encoder/QpEncoder.php | 263 + .../classes/Swift/Encoder/Rfc2231Encoder.php | 89 + lib/Swift/lib/classes/Swift/Encoding.php | 70 + .../lib/classes/Swift/Events/CommandEvent.php | 67 + .../classes/Swift/Events/CommandListener.php | 29 + lib/Swift/lib/classes/Swift/Events/Event.php | 39 + .../classes/Swift/Events/EventDispatcher.php | 81 + .../classes/Swift/Events/EventListener.php | 19 + .../lib/classes/Swift/Events/EventObject.php | 65 + .../classes/Swift/Events/ResponseEvent.php | 65 + .../classes/Swift/Events/ResponseListener.php | 29 + .../lib/classes/Swift/Events/SendEvent.php | 127 + .../lib/classes/Swift/Events/SendListener.php | 35 + .../Swift/Events/SimpleEventDispatcher.php | 175 + .../Swift/Events/TransportChangeEvent.php | 31 + .../Swift/Events/TransportChangeListener.php | 53 + .../Swift/Events/TransportExceptionEvent.php | 50 + .../Events/TransportExceptionListener.php | 30 + .../lib/classes/Swift/FailoverTransport.php | 48 + lib/Swift/lib/classes/Swift/FileStream.php | 28 + lib/Swift/lib/classes/Swift/Filterable.php | 34 + lib/Swift/lib/classes/Swift/Image.php | 62 + .../lib/classes/Swift/InputByteStream.php | 72 + lib/Swift/lib/classes/Swift/IoException.php | 30 + lib/Swift/lib/classes/Swift/KeyCache.php | 99 + .../classes/Swift/KeyCache/ArrayKeyCache.php | 209 + .../classes/Swift/KeyCache/DiskKeyCache.php | 316 + .../Swift/KeyCache/KeyCacheInputStream.php | 53 + .../classes/Swift/KeyCache/NullKeyCache.php | 110 + .../KeyCache/SimpleKeyCacheInputStream.php | 131 + .../classes/Swift/LoadBalancedTransport.php | 48 + lib/Swift/lib/classes/Swift/MailTransport.php | 48 + lib/Swift/lib/classes/Swift/Mailer.php | 173 + .../Swift/Mailer/ArrayRecipientIterator.php | 59 + .../Swift/Mailer/RecipientIterator.php | 34 + lib/Swift/lib/classes/Swift/Message.php | 82 + .../lib/classes/Swift/Mime/Attachment.php | 143 + .../classes/Swift/Mime/CharsetObserver.php | 26 + .../lib/classes/Swift/Mime/ContentEncoder.php | 41 + .../ContentEncoder/Base64ContentEncoder.php | 81 + .../ContentEncoder/PlainContentEncoder.php | 175 + .../Mime/ContentEncoder/QpContentEncoder.php | 117 + .../lib/classes/Swift/Mime/EmbeddedFile.php | 51 + .../classes/Swift/Mime/EncodingObserver.php | 28 + lib/Swift/lib/classes/Swift/Mime/Header.php | 85 + .../lib/classes/Swift/Mime/HeaderEncoder.php | 28 + .../HeaderEncoder/Base64HeaderEncoder.php | 36 + .../Mime/HeaderEncoder/QpHeaderEncoder.php | 99 + .../lib/classes/Swift/Mime/HeaderFactory.php | 72 + .../lib/classes/Swift/Mime/HeaderSet.php | 170 + .../Swift/Mime/Headers/AbstractHeader.php | 596 + .../classes/Swift/Mime/Headers/DateHeader.php | 118 + .../Mime/Headers/IdentificationHeader.php | 161 + .../Swift/Mime/Headers/MailboxHeader.php | 316 + .../Mime/Headers/ParameterizedHeader.php | 274 + .../classes/Swift/Mime/Headers/PathHeader.php | 126 + .../Swift/Mime/Headers/UnstructuredHeader.php | 108 + lib/Swift/lib/classes/Swift/Mime/Message.php | 230 + .../lib/classes/Swift/Mime/MimeEntity.php | 108 + lib/Swift/lib/classes/Swift/Mime/MimePart.php | 196 + .../Swift/Mime/ParameterizedHeader.php | 35 + .../Swift/Mime/SimpleHeaderFactory.php | 187 + .../classes/Swift/Mime/SimpleHeaderSet.php | 396 + .../lib/classes/Swift/Mime/SimpleMessage.php | 609 + .../classes/Swift/Mime/SimpleMimeEntity.php | 803 + lib/Swift/lib/classes/Swift/MimePart.php | 65 + .../lib/classes/Swift/OutputByteStream.php | 41 + .../classes/Swift/Plugins/AntiFloodPlugin.php | 147 + .../Swift/Plugins/BandwidthMonitorPlugin.php | 173 + .../Swift/Plugins/Decorator/Replacements.php | 36 + .../classes/Swift/Plugins/DecoratorPlugin.php | 201 + .../lib/classes/Swift/Plugins/Logger.php | 37 + .../classes/Swift/Plugins/LoggerPlugin.php | 160 + .../Swift/Plugins/Loggers/ArrayLogger.php | 73 + .../Swift/Plugins/Loggers/EchoLogger.php | 64 + .../Swift/Plugins/Pop/Pop3Connection.php | 36 + .../Swift/Plugins/Pop/Pop3Exception.php | 34 + .../Swift/Plugins/PopBeforeSmtpPlugin.php | 288 + .../lib/classes/Swift/Plugins/Reporter.php | 36 + .../classes/Swift/Plugins/ReporterPlugin.php | 82 + .../Swift/Plugins/Reporters/HitReporter.php | 63 + .../Swift/Plugins/Reporters/HtmlReporter.php | 47 + .../lib/classes/Swift/Plugins/Sleeper.php | 26 + .../classes/Swift/Plugins/ThrottlerPlugin.php | 188 + lib/Swift/lib/classes/Swift/Plugins/Timer.php | 26 + lib/Swift/lib/classes/Swift/Preferences.php | 76 + .../Swift/ReplacementFilterFactory.php | 27 + .../classes/Swift/RfcComplianceException.php | 30 + .../lib/classes/Swift/SendmailTransport.php | 48 + lib/Swift/lib/classes/Swift/SmtpTransport.php | 56 + lib/Swift/lib/classes/Swift/StreamFilter.php | 33 + .../ByteArrayReplacementFilter.php | 188 + .../StreamFilters/StringReplacementFilter.php | 66 + .../StringReplacementFilterFactory.php | 53 + .../lib/classes/Swift/SwiftException.php | 28 + lib/Swift/lib/classes/Swift/Transport.php | 60 + .../Swift/Transport/AbstractSmtpTransport.php | 543 + .../Esmtp/Auth/CramMd5Authenticator.php | 88 + .../Esmtp/Auth/LoginAuthenticator.php | 58 + .../Esmtp/Auth/PlainAuthenticator.php | 57 + .../Swift/Transport/Esmtp/AuthHandler.php | 262 + .../Swift/Transport/Esmtp/Authenticator.php | 38 + .../classes/Swift/Transport/EsmtpHandler.php | 82 + .../Swift/Transport/EsmtpTransport.php | 340 + .../Swift/Transport/FailoverTransport.php | 97 + .../lib/classes/Swift/Transport/IoBuffer.php | 65 + .../Swift/Transport/LoadBalancedTransport.php | 188 + .../classes/Swift/Transport/MailInvoker.php | 36 + .../classes/Swift/Transport/MailTransport.php | 242 + .../Swift/Transport/SendmailTransport.php | 173 + .../Swift/Transport/SimpleMailInvoker.php | 58 + .../lib/classes/Swift/Transport/SmtpAgent.php | 36 + .../classes/Swift/Transport/StreamBuffer.php | 276 + .../lib/classes/Swift/TransportException.php | 31 + lib/Swift/lib/dependency_maps/cache_deps.php | 25 + lib/Swift/lib/dependency_maps/mime_deps.php | 97 + .../lib/dependency_maps/transport_deps.php | 62 + lib/Swift/lib/mime_types.php | 76 + lib/Swift/lib/preferences.php | 20 + lib/Swift/lib/swift_init.php | 21 + lib/Swift/lib/swift_required.php | 22 + lib/Swift/lib/swift_required_pear.php | 22 + lib/Swift/test-suite/CHANGES | 7 + lib/Swift/test-suite/LICENSE | 165 + lib/Swift/test-suite/README | 159 + lib/Swift/test-suite/config.php | 65 + lib/Swift/test-suite/index.php | 40 + lib/Swift/test-suite/lib/Sweety/Reporter.php | 69 + .../lib/Sweety/Reporter/CliReporter.php | 203 + .../Sweety/Reporter/CliTestCaseReporter.php | 160 + .../lib/Sweety/Reporter/HtmlReporter.php | 174 + .../Sweety/Reporter/HtmlTestCaseReporter.php | 174 + lib/Swift/test-suite/lib/Sweety/Runner.php | 56 + .../lib/Sweety/Runner/AbstractTestRunner.php | 365 + .../lib/Sweety/Runner/CliRunner.php | 128 + .../lib/Sweety/Runner/HtmlRunner.php | 160 + .../test-suite/lib/Sweety/TestLocator.php | 25 + .../Sweety/TestLocator/PearStyleLocator.php | 71 + .../HELP_MY_TESTS_DONT_WORK_ANYMORE | 383 + lib/Swift/test-suite/lib/simpletest/LICENSE | 502 + lib/Swift/test-suite/lib/simpletest/README | 108 + lib/Swift/test-suite/lib/simpletest/TODO.xml | 176 + lib/Swift/test-suite/lib/simpletest/VERSION | 1 + .../lib/simpletest/authentication.php | 237 + .../test-suite/lib/simpletest/autorun.php | 97 + .../test-suite/lib/simpletest/browser.php | 1094 ++ .../test-suite/lib/simpletest/collector.php | 122 + .../lib/simpletest/compatibility.php | 166 + .../test-suite/lib/simpletest/cookies.php | 380 + .../lib/simpletest/default_reporter.php | 163 + .../test-suite/lib/simpletest/detached.php | 96 + .../test-suite/lib/simpletest/dumper.php | 359 + .../test-suite/lib/simpletest/eclipse.php | 307 + .../test-suite/lib/simpletest/encoding.php | 552 + .../test-suite/lib/simpletest/errors.php | 257 + .../test-suite/lib/simpletest/exceptions.php | 198 + .../test-suite/lib/simpletest/expectation.php | 901 ++ lib/Swift/test-suite/lib/simpletest/form.php | 355 + .../test-suite/lib/simpletest/frames.php | 592 + lib/Swift/test-suite/lib/simpletest/http.php | 628 + .../test-suite/lib/simpletest/invoker.php | 139 + .../lib/simpletest/mock_objects.php | 1630 ++ lib/Swift/test-suite/lib/simpletest/page.php | 979 ++ .../test-suite/lib/simpletest/parser.php | 760 + .../lib/simpletest/reflection_php4.php | 136 + .../lib/simpletest/reflection_php5.php | 386 + .../test-suite/lib/simpletest/remote.php | 115 + .../test-suite/lib/simpletest/reporter.php | 446 + .../test-suite/lib/simpletest/scorer.php | 862 + .../test-suite/lib/simpletest/selector.php | 141 + .../lib/simpletest/shell_tester.php | 330 + .../test-suite/lib/simpletest/simpletest.php | 396 + .../test-suite/lib/simpletest/socket.php | 308 + lib/Swift/test-suite/lib/simpletest/tag.php | 1418 ++ .../test-suite/lib/simpletest/test_case.php | 655 + .../test-suite/lib/simpletest/unit_tester.php | 402 + lib/Swift/test-suite/lib/simpletest/url.php | 550 + .../test-suite/lib/simpletest/user_agent.php | 328 + .../test-suite/lib/simpletest/web_tester.php | 1495 ++ lib/Swift/test-suite/lib/simpletest/xml.php | 647 + .../test-suite/lib/yaymock/classes/Yay.php | 261 + .../lib/yaymock/classes/Yay/Action.php | 37 + .../classes/Yay/Actions/CallbackAction.php | 66 + .../Yay/Actions/ReturnReferenceAction.php | 64 + .../classes/Yay/Actions/ReturnValueAction.php | 100 + .../classes/Yay/Actions/ThrowAction.php | 66 + .../lib/yaymock/classes/Yay/Description.php | 45 + .../lib/yaymock/classes/Yay/Expectation.php | 88 + .../classes/Yay/ExpectationProvider.php | 33 + .../lib/yaymock/classes/Yay/Expectations.php | 306 + .../Yay/Expectations/AbstractExpectation.php | 345 + .../Yay/Expectations/AtLeastExpectation.php | 101 + .../Yay/Expectations/AtMostExpectation.php | 117 + .../Yay/Expectations/BetweenExpectation.php | 118 + .../Yay/Expectations/ExactlyExpectation.php | 117 + .../lib/yaymock/classes/Yay/Invocation.php | 49 + .../yaymock/classes/Yay/InvocationHandler.php | 36 + .../yaymock/classes/Yay/InvocationProxy.php | 90 + .../classes/Yay/InvocationRecorder.php | 36 + .../lib/yaymock/classes/Yay/Matcher.php | 48 + .../classes/Yay/Matchers/AnyMatcher.php | 85 + .../classes/Yay/Matchers/BoundsMatcher.php | 94 + .../classes/Yay/Matchers/EqualMatcher.php | 50 + .../classes/Yay/Matchers/IdenticalMatcher.php | 116 + .../classes/Yay/Matchers/OptionalMatcher.php | 103 + .../classes/Yay/Matchers/PatternMatcher.php | 88 + .../classes/Yay/Matchers/ReferenceMatcher.php | 104 + .../lib/yaymock/classes/Yay/MockGenerator.php | 323 + .../lib/yaymock/classes/Yay/MockObject.php | 25 + .../lib/yaymock/classes/Yay/Mockery.php | 183 + .../classes/Yay/NotSatisfiedException.php | 36 + .../yaymock/classes/Yay/SelfDescribing.php | 35 + .../lib/yaymock/classes/Yay/Sequence.php | 43 + .../yaymock/classes/Yay/SimpleDescription.php | 63 + .../yaymock/classes/Yay/SimpleInvocation.php | 163 + .../yaymock/classes/Yay/SimpleSequence.php | 108 + .../lib/yaymock/classes/Yay/SimpleState.php | 49 + .../classes/Yay/SimpleStatePredicate.php | 88 + .../lib/yaymock/classes/Yay/State.php | 34 + .../lib/yaymock/classes/Yay/StateMachine.php | 113 + .../yaymock/classes/Yay/StatePredicate.php | 35 + .../lib/yaymock/classes/Yay/States.php | 62 + lib/Swift/test-suite/lib/yaymock/mock.tpl.php | 69 + .../lib/yaymock/yay_convenience.php | 176 + lib/Swift/test-suite/lib/yaymock/yay_mock.php | 29 + lib/Swift/test-suite/run.php | 56 + lib/Swift/test-suite/sweety.js | 471 + .../test-suite/templates/sweety/css/main.css | 203 + .../templates/sweety/images/darr.gif | Bin 0 -> 57 bytes .../templates/sweety/images/group.gif | Bin 0 -> 90 bytes .../templates/sweety/images/htmlicon.gif | Bin 0 -> 1022 bytes .../templates/sweety/images/loading.gif | Bin 0 -> 1732 bytes .../templates/sweety/images/network.gif | Bin 0 -> 2130 bytes .../templates/sweety/images/rarr.gif | Bin 0 -> 59 bytes .../templates/sweety/images/runicon.gif | Bin 0 -> 159 bytes .../templates/sweety/images/xmlicon.gif | Bin 0 -> 128 bytes .../templates/sweety/js/sweety-template.js | 1155 ++ .../templates/sweety/suite-ui-noajax.tpl.php | 155 + .../templates/sweety/suite-ui.tpl.php | 132 + lib/Swift/test-suite/xpath-legacy.js | 2764 ++++ .../_samples/charsets/iso-8859-1/one.txt | 19 + .../tests/_samples/charsets/utf-8/one.txt | 22 + .../tests/_samples/charsets/utf-8/three.txt | 45 + .../tests/_samples/charsets/utf-8/two.txt | 3 + lib/Swift/tests/_samples/files/data.txt | 1 + lib/Swift/tests/_samples/files/textfile.zip | Bin 0 -> 202 bytes lib/Swift/tests/acceptance.conf.php | 44 + .../Swift/AttachmentAcceptanceTest.php | 15 + .../FileByteStreamAcceptanceTest.php | 174 + ...leCharacterReaderFactoryAcceptanceTest.php | 204 + .../DependencyContainerAcceptanceTest.php | 28 + .../Swift/EmbeddedFileAcceptanceTest.php | 15 + .../Encoder/Base64EncoderAcceptanceTest.php | 56 + .../Swift/Encoder/QpEncoderAcceptanceTest.php | 65 + .../Encoder/Rfc2231EncoderAcceptanceTest.php | 63 + .../Swift/EncodingAcceptanceTest.php | 33 + .../KeyCache/ArrayKeyCacheAcceptanceTest.php | 182 + .../KeyCache/DiskKeyCacheAcceptanceTest.php | 192 + .../Swift/MessageAcceptanceTest.php | 59 + .../Swift/Mime/AttachmentAcceptanceTest.php | 134 + .../Base64ContentEncoderAcceptanceTest.php | 70 + .../PlainContentEncoderAcceptanceTest.php | 109 + .../QpContentEncoderAcceptanceTest.php | 163 + .../Swift/Mime/EmbeddedFileAcceptanceTest.php | 147 + .../Swift/Mime/MimePartAcceptanceTest.php | 140 + .../Mime/SimpleMessageAcceptanceTest.php | 1256 ++ .../Swift/MimePartAcceptanceTest.php | 16 + .../AbstractStreamBufferAcceptanceTest.php | 124 + .../BasicSocketAcceptanceTest.php | 33 + .../StreamBuffer/ProcessAcceptanceTest.php | 25 + .../StreamBuffer/SslSocketAcceptanceTest.php | 37 + .../StreamBuffer/TlsSocketAcceptanceTest.php | 37 + lib/Swift/tests/bug/Swift/Bug118Test.php | 24 + lib/Swift/tests/bug/Swift/Bug34Test.php | 79 + lib/Swift/tests/bug/Swift/Bug35Test.php | 77 + lib/Swift/tests/bug/Swift/Bug38Test.php | 199 + lib/Swift/tests/bug/Swift/Bug51Test.php | 133 + lib/Swift/tests/bug/Swift/Bug71Test.php | 24 + lib/Swift/tests/bug/Swift/Bug76Test.php | 90 + .../Tests/IdenticalBinaryExpectation.php | 84 + .../Swift/Tests/SwiftSmokeTestCase.php | 55 + .../helpers/Swift/Tests/SwiftUnitTestCase.php | 103 + lib/Swift/tests/smoke.conf.php | 63 + .../smoke/Swift/Smoke/AttachmentSmokeTest.php | 32 + .../smoke/Swift/Smoke/BasicSmokeTest.php | 26 + .../Smoke/HtmlWithAttachmentSmokeTest.php | 32 + .../Swift/Smoke/InternationalSmokeTest.php | 39 + .../Swift/ByteStream/ArrayByteStreamTest.php | 211 + .../GenericFixedWidthReaderTest.php | 48 + .../CharacterReader/UsAsciiReaderTest.php | 64 + .../Swift/CharacterReader/Utf8ReaderTest.php | 76 + .../ArrayCharacterStreamTest.php | 376 + .../unit/Swift/DependencyContainerTest.php | 177 + .../unit/Swift/Encoder/Base64EncoderTest.php | 180 + .../unit/Swift/Encoder/QpEncoderTest.php | 372 + .../unit/Swift/Encoder/Rfc2231EncoderTest.php | 152 + .../unit/Swift/Events/CommandEventTest.php | 43 + .../unit/Swift/Events/EventObjectTest.php | 39 + .../unit/Swift/Events/ResponseEventTest.php | 46 + .../tests/unit/Swift/Events/SendEventTest.php | 97 + .../Events/SimpleEventDispatcherTest.php | 168 + .../Swift/Events/TransportChangeEventTest.php | 38 + .../Events/TransportExceptionEventTest.php | 51 + .../unit/Swift/KeyCache/ArrayKeyCacheTest.php | 244 + .../SimpleKeyCacheInputStreamTest.php | 79 + .../Mailer/ArrayRecipientIteratorTest.php | 48 + lib/Swift/tests/unit/Swift/MailerTest.php | 293 + .../Swift/Mime/AbstractMimeEntityTest.php | 1110 ++ .../tests/unit/Swift/Mime/AttachmentTest.php | 299 + .../Base64ContentEncoderTest.php | 298 + .../PlainContentEncoderTest.php | 281 + .../ContentEncoder/QpContentEncoderTest.php | 482 + .../unit/Swift/Mime/EmbeddedFileTest.php | 64 + .../HeaderEncoder/Base64HeaderEncoderTest.php | 17 + .../HeaderEncoder/QpHeaderEncoderTest.php | 231 + .../Swift/Mime/Headers/DateHeaderTest.php | 77 + .../Mime/Headers/IdentificationHeaderTest.php | 209 + .../Swift/Mime/Headers/MailboxHeaderTest.php | 340 + .../Mime/Headers/ParameterizedHeaderTest.php | 425 + .../Swift/Mime/Headers/PathHeaderTest.php | 87 + .../Mime/Headers/UnstructuredHeaderTest.php | 368 + .../tests/unit/Swift/Mime/MimePartTest.php | 253 + .../Swift/Mime/SimpleHeaderFactoryTest.php | 179 + .../unit/Swift/Mime/SimpleHeaderSetTest.php | 654 + .../unit/Swift/Mime/SimpleMessageTest.php | 804 + .../unit/Swift/Mime/SimpleMimeEntityTest.php | 16 + .../Swift/Plugins/AntiFloodPluginTest.php | 103 + .../Plugins/BandwidthMonitorPluginTest.php | 127 + .../Swift/Plugins/DecoratorPluginTest.php | 194 + .../unit/Swift/Plugins/LoggerPluginTest.php | 199 + .../Swift/Plugins/Loggers/ArrayLoggerTest.php | 71 + .../Swift/Plugins/Loggers/EchoLoggerTest.php | 30 + .../Swift/Plugins/PopBeforeSmtpPluginTest.php | 115 + .../unit/Swift/Plugins/ReporterPluginTest.php | 122 + .../Plugins/Reporters/HitReporterTest.php | 71 + .../Plugins/Reporters/HtmlReporterTest.php | 61 + .../Swift/Plugins/ThrottlerPluginTest.php | 129 + .../ByteArrayReplacementFilterTest.php | 138 + .../StringReplacementFilterFactoryTest.php | 44 + .../StringReplacementFilterTest.php | 62 + .../AbstractSmtpEventSupportTest.php | 383 + .../unit/Swift/Transport/AbstractSmtpTest.php | 962 ++ .../Esmtp/Auth/CramMd5AuthenticatorTest.php | 69 + .../Esmtp/Auth/LoginAuthenticatorTest.php | 62 + .../Esmtp/Auth/PlainAuthenticatorTest.php | 73 + .../Swift/Transport/Esmtp/AuthHandlerTest.php | 158 + .../EsmtpTransport/ExtensionSupportTest.php | 314 + .../Swift/Transport/EsmtpTransportTest.php | 242 + .../Swift/Transport/FailoverTransportTest.php | 345 + .../Transport/LoadBalancedTransportTest.php | 428 + .../Swift/Transport/MailTransportTest.php | 319 + .../Swift/Transport/SendmailTransportTest.php | 137 + .../unit/Swift/Transport/StreamBufferTest.php | 52 + lib/Twig-1.0.0-RC1/AUTHORS | 9 + lib/Twig-1.0.0-RC1/CHANGELOG | 277 + lib/Twig-1.0.0-RC1/LICENSE | 31 + lib/Twig-1.0.0-RC1/README.markdown | 8 + .../bin/create_pear_package.php | 42 + lib/Twig-1.0.0-RC1/doc/advanced.rst | 416 + lib/Twig-1.0.0-RC1/doc/api.rst | 474 + lib/Twig-1.0.0-RC1/doc/extensions.rst | 302 + lib/Twig-1.0.0-RC1/doc/hacking.rst | 184 + lib/Twig-1.0.0-RC1/doc/index.rst | 13 + lib/Twig-1.0.0-RC1/doc/intro.rst | 99 + lib/Twig-1.0.0-RC1/doc/recipes.rst | 243 + lib/Twig-1.0.0-RC1/doc/templates.rst | 1434 ++ lib/Twig-1.0.0-RC1/lib/Twig/Autoloader.php | 46 + lib/Twig-1.0.0-RC1/lib/Twig/Compiler.php | 220 + .../lib/Twig/CompilerInterface.php | 35 + lib/Twig-1.0.0-RC1/lib/Twig/Environment.php | 893 + lib/Twig-1.0.0-RC1/lib/Twig/Error.php | 76 + lib/Twig-1.0.0-RC1/lib/Twig/Error/Loader.php | 20 + lib/Twig-1.0.0-RC1/lib/Twig/Error/Runtime.php | 21 + lib/Twig-1.0.0-RC1/lib/Twig/Error/Syntax.php | 21 + .../lib/Twig/ExpressionParser.php | 374 + lib/Twig-1.0.0-RC1/lib/Twig/Extension.php | 93 + .../lib/Twig/Extension/Core.php | 497 + .../lib/Twig/Extension/Escaper.php | 73 + .../lib/Twig/Extension/Optimizer.php | 35 + .../lib/Twig/Extension/Sandbox.php | 103 + .../lib/Twig/ExtensionInterface.php | 84 + lib/Twig-1.0.0-RC1/lib/Twig/Filter.php | 58 + .../lib/Twig/Filter/Function.php | 33 + lib/Twig-1.0.0-RC1/lib/Twig/Filter/Method.php | 34 + .../lib/Twig/FilterInterface.php | 32 + lib/Twig-1.0.0-RC1/lib/Twig/Function.php | 52 + .../lib/Twig/Function/Function.php | 34 + .../lib/Twig/Function/Method.php | 35 + .../lib/Twig/FunctionInterface.php | 31 + lib/Twig-1.0.0-RC1/lib/Twig/Lexer.php | 306 + .../lib/Twig/LexerInterface.php | 29 + lib/Twig-1.0.0-RC1/lib/Twig/Loader/Array.php | 84 + .../lib/Twig/Loader/Filesystem.php | 133 + lib/Twig-1.0.0-RC1/lib/Twig/Loader/String.php | 59 + .../lib/Twig/LoaderInterface.php | 45 + lib/Twig-1.0.0-RC1/lib/Twig/Markup.php | 31 + lib/Twig-1.0.0-RC1/lib/Twig/Node.php | 227 + .../lib/Twig/Node/AutoEscape.php | 40 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Block.php | 45 + .../lib/Twig/Node/BlockReference.php | 38 + .../lib/Twig/Node/Expression.php | 21 + .../lib/Twig/Node/Expression/Array.php | 41 + .../lib/Twig/Node/Expression/AssignName.php | 24 + .../lib/Twig/Node/Expression/Binary.php | 40 + .../lib/Twig/Node/Expression/Binary/Add.php | 18 + .../lib/Twig/Node/Expression/Binary/And.php | 18 + .../Twig/Node/Expression/Binary/Concat.php | 18 + .../lib/Twig/Node/Expression/Binary/Div.php | 18 + .../lib/Twig/Node/Expression/Binary/Equal.php | 17 + .../Twig/Node/Expression/Binary/FloorDiv.php | 29 + .../Twig/Node/Expression/Binary/Greater.php | 17 + .../Node/Expression/Binary/GreaterEqual.php | 17 + .../lib/Twig/Node/Expression/Binary/In.php | 33 + .../lib/Twig/Node/Expression/Binary/Less.php | 17 + .../Twig/Node/Expression/Binary/LessEqual.php | 17 + .../lib/Twig/Node/Expression/Binary/Mod.php | 18 + .../lib/Twig/Node/Expression/Binary/Mul.php | 18 + .../Twig/Node/Expression/Binary/NotEqual.php | 17 + .../lib/Twig/Node/Expression/Binary/NotIn.php | 33 + .../lib/Twig/Node/Expression/Binary/Or.php | 18 + .../lib/Twig/Node/Expression/Binary/Power.php | 33 + .../lib/Twig/Node/Expression/Binary/Range.php | 33 + .../lib/Twig/Node/Expression/Binary/Sub.php | 18 + .../Twig/Node/Expression/BlockReference.php | 39 + .../lib/Twig/Node/Expression/Conditional.php | 31 + .../lib/Twig/Node/Expression/Constant.php | 23 + .../Node/Expression/ExtensionReference.php | 34 + .../lib/Twig/Node/Expression/Filter.php | 72 + .../lib/Twig/Node/Expression/Function.php | 43 + .../lib/Twig/Node/Expression/GetAttr.php | 47 + .../lib/Twig/Node/Expression/Name.php | 33 + .../lib/Twig/Node/Expression/Parent.php | 39 + .../lib/Twig/Node/Expression/Test.php | 62 + .../lib/Twig/Node/Expression/Unary.php | 30 + .../lib/Twig/Node/Expression/Unary/Neg.php | 18 + .../lib/Twig/Node/Expression/Unary/Not.php | 18 + .../lib/Twig/Node/Expression/Unary/Pos.php | 18 + lib/Twig-1.0.0-RC1/lib/Twig/Node/For.php | 119 + lib/Twig-1.0.0-RC1/lib/Twig/Node/If.php | 67 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Import.php | 51 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Include.php | 76 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Macro.php | 65 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Module.php | 205 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Print.php | 40 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Sandbox.php | 48 + .../lib/Twig/Node/SandboxedModule.php | 71 + .../lib/Twig/Node/SandboxedPrint.php | 68 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Set.php | 79 + .../lib/Twig/Node/Spaceless.php | 41 + lib/Twig-1.0.0-RC1/lib/Twig/Node/Text.php | 40 + lib/Twig-1.0.0-RC1/lib/Twig/NodeInterface.php | 30 + .../lib/Twig/NodeOutputInterface.php | 20 + lib/Twig-1.0.0-RC1/lib/Twig/NodeTraverser.php | 89 + .../lib/Twig/NodeVisitor/Escaper.php | 161 + .../lib/Twig/NodeVisitor/Optimizer.php | 173 + .../lib/Twig/NodeVisitor/SafeAnalysis.php | 101 + .../lib/Twig/NodeVisitor/Sandbox.php | 93 + .../lib/Twig/NodeVisitorInterface.php | 48 + lib/Twig-1.0.0-RC1/lib/Twig/Parser.php | 287 + .../lib/Twig/ParserInterface.php | 28 + .../lib/Twig/Sandbox/SecurityError.php | 20 + .../lib/Twig/Sandbox/SecurityPolicy.php | 116 + .../Twig/Sandbox/SecurityPolicyInterface.php | 25 + lib/Twig-1.0.0-RC1/lib/Twig/Template.php | 294 + .../lib/Twig/TemplateInterface.php | 42 + lib/Twig-1.0.0-RC1/lib/Twig/Test/Function.php | 31 + lib/Twig-1.0.0-RC1/lib/Twig/Test/Method.php | 32 + lib/Twig-1.0.0-RC1/lib/Twig/TestInterface.php | 26 + lib/Twig-1.0.0-RC1/lib/Twig/Token.php | 208 + lib/Twig-1.0.0-RC1/lib/Twig/TokenParser.php | 31 + .../lib/Twig/TokenParser/AutoEscape.php | 58 + .../lib/Twig/TokenParser/Block.php | 72 + .../lib/Twig/TokenParser/Extends.php | 42 + .../lib/Twig/TokenParser/Filter.php | 51 + .../lib/Twig/TokenParser/For.php | 68 + .../lib/Twig/TokenParser/From.php | 66 + .../lib/Twig/TokenParser/If.php | 79 + .../lib/Twig/TokenParser/Import.php | 39 + .../lib/Twig/TokenParser/Include.php | 53 + .../lib/Twig/TokenParser/Macro.php | 52 + .../lib/Twig/TokenParser/Sandbox.php | 43 + .../lib/Twig/TokenParser/Set.php | 66 + .../lib/Twig/TokenParser/Spaceless.php | 45 + .../lib/Twig/TokenParserBroker.php | 107 + .../lib/Twig/TokenParserBrokerInterface.php | 45 + .../lib/Twig/TokenParserInterface.php | 42 + lib/Twig-1.0.0-RC1/lib/Twig/TokenStream.php | 139 + lib/Twig-1.0.0-RC1/package.xml.tpl | 63 + lib/Twig-1.0.0-RC1/phpunit.xml | 25 + .../test/Twig/Tests/AutoloaderTest.php | 21 + .../test/Twig/Tests/ExpressionParserTest.php | 124 + .../test/Twig/Tests/Extension/SandboxTest.php | 168 + .../test/Twig/Tests/FileCachingTest.php | 47 + .../Tests/Fixtures/expressions/array.test | 45 + .../Fixtures/expressions/array_call.test | 14 + .../Tests/Fixtures/expressions/binary.test | 46 + .../Fixtures/expressions/comparison.test | 14 + .../Tests/Fixtures/expressions/dotdot.test | 17 + .../Tests/Fixtures/expressions/grouping.test | 8 + .../Twig/Tests/Fixtures/expressions/in.test | 20 + .../Fixtures/expressions/magic_call.test | 27 + .../Fixtures/expressions/method_call.test | 26 + .../Tests/Fixtures/expressions/postfix.test | 21 + .../expressions/ternary_operator.test | 16 + .../Tests/Fixtures/expressions/unary.test | 12 + .../Twig/Tests/Fixtures/filters/date.test | 23 + .../Twig/Tests/Fixtures/filters/default.test | 14 + .../Twig/Tests/Fixtures/filters/format.test | 8 + .../Twig/Tests/Fixtures/filters/length.test | 10 + .../Twig/Tests/Fixtures/filters/merge.test | 14 + .../Twig/Tests/Fixtures/filters/sort.test | 10 + .../Tests/Fixtures/functions/constant.test | 12 + .../Twig/Tests/Fixtures/functions/cycle.test | 16 + .../Tests/Fixtures/tags/autoescape/basic.test | 22 + .../Fixtures/tags/autoescape/blocks.test | 12 + .../tags/autoescape/double_escaping.test | 10 + .../Fixtures/tags/autoescape/functions.test | 83 + .../Fixtures/tags/autoescape/literal.test | 45 + .../Fixtures/tags/autoescape/nested.test | 26 + .../Fixtures/tags/autoescape/objects.test | 26 + .../Fixtures/tags/autoescape/strategy.test | 11 + .../Tests/Fixtures/tags/autoescape/type.test | 69 + .../tags/autoescape/with_filters.test | 131 + .../autoescape/with_filters_arguments.test | 23 + .../autoescape/with_pre_escape_filters.test | 68 + .../Twig/Tests/Fixtures/tags/block/basic.test | 11 + .../Tests/Fixtures/tags/filter/basic.test | 10 + .../Tests/Fixtures/tags/filter/multiple.test | 10 + .../Tests/Fixtures/tags/filter/nested.test | 16 + .../Fixtures/tags/filter/with_for_tag.test | 13 + .../Fixtures/tags/filter/with_if_tag.test | 29 + .../Twig/Tests/Fixtures/tags/for/context.test | 18 + .../Twig/Tests/Fixtures/tags/for/else.test | 21 + .../Fixtures/tags/for/inner_variables.test | 17 + .../Twig/Tests/Fixtures/tags/for/keys.test | 11 + .../Fixtures/tags/for/keys_and_values.test | 11 + .../Tests/Fixtures/tags/for/loop_context.test | 19 + .../Fixtures/tags/for/loop_context_local.test | 10 + .../Tests/Fixtures/tags/for/nested_else.test | 17 + .../Twig/Tests/Fixtures/tags/for/objects.test | 43 + .../Fixtures/tags/for/objects_countable.test | 47 + .../Tests/Fixtures/tags/for/recursive.test | 18 + .../Twig/Tests/Fixtures/tags/for/values.test | 11 + .../test/Twig/Tests/Fixtures/tags/from.test | 14 + .../Twig/Tests/Fixtures/tags/if/basic.test | 22 + .../Tests/Fixtures/tags/if/expression.test | 22 + .../Tests/Fixtures/tags/include/basic.test | 16 + .../Fixtures/tags/include/expression.test | 16 + .../Tests/Fixtures/tags/include/only.test | 16 + .../tags/include/template_instance.test | 10 + .../Fixtures/tags/include/with_variables.test | 12 + .../Fixtures/tags/inheritance/basic.test | 14 + .../tags/inheritance/conditional.test | 14 + .../Fixtures/tags/inheritance/dynamic.test | 14 + .../Fixtures/tags/inheritance/multiple.test | 12 + .../tags/inheritance/nested_inheritance.test | 16 + .../Fixtures/tags/inheritance/parent.test | 12 + .../tags/inheritance/parent_isolation.test | 20 + .../tags/inheritance/parent_nested.test | 28 + .../tags/inheritance/template_instance.test | 14 + .../Twig/Tests/Fixtures/tags/macro/basic.test | 15 + .../Tests/Fixtures/tags/macro/external.test | 17 + .../Twig/Tests/Fixtures/tags/macro/from.test | 18 + .../Fixtures/tags/macro/self_import.test | 17 + .../test/Twig/Tests/Fixtures/tags/raw.test | 10 + .../Twig/Tests/Fixtures/tags/set/basic.test | 17 + .../Twig/Tests/Fixtures/tags/set/capture.test | 10 + .../Tests/Fixtures/tags/set/expression.test | 12 + .../Tests/Fixtures/tags/spaceless/simple.test | 12 + .../Twig/Tests/Fixtures/tests/constant.test | 12 + .../Twig/Tests/Fixtures/tests/defined.test | 16 + .../test/Twig/Tests/Fixtures/tests/empty.test | 12 + .../test/Twig/Tests/Fixtures/tests/even.test | 14 + .../test/Twig/Tests/Fixtures/tests/odd.test | 10 + .../test/Twig/Tests/LexerTest.php | 39 + .../test/Twig/Tests/Loader/FilesystemTest.php | 46 + .../test/Twig/Tests/Node/AutoEscapeTest.php | 46 + .../Twig/Tests/Node/BlockReferenceTest.php | 41 + .../test/Twig/Tests/Node/BlockTest.php | 52 + .../Twig/Tests/Node/Expression/ArrayTest.php | 48 + .../Tests/Node/Expression/AssignNameTest.php | 43 + .../Tests/Node/Expression/Binary/AddTest.php | 49 + .../Tests/Node/Expression/Binary/AndTest.php | 49 + .../Node/Expression/Binary/ConcatTest.php | 49 + .../Tests/Node/Expression/Binary/DivTest.php | 49 + .../Node/Expression/Binary/FloorDivTest.php | 49 + .../Tests/Node/Expression/Binary/ModTest.php | 49 + .../Tests/Node/Expression/Binary/MulTest.php | 49 + .../Tests/Node/Expression/Binary/OrTest.php | 49 + .../Tests/Node/Expression/Binary/SubTest.php | 49 + .../Tests/Node/Expression/ConditionalTest.php | 52 + .../Tests/Node/Expression/ConstantTest.php | 44 + .../Twig/Tests/Node/Expression/FilterTest.php | 75 + .../Tests/Node/Expression/GetAttrTest.php | 67 + .../Twig/Tests/Node/Expression/NameTest.php | 50 + .../Twig/Tests/Node/Expression/ParentTest.php | 42 + .../Tests/Node/Expression/Unary/NegTest.php | 46 + .../Tests/Node/Expression/Unary/NotTest.php | 46 + .../Tests/Node/Expression/Unary/PosTest.php | 46 + .../test/Twig/Tests/Node/ForTest.php | 163 + .../test/Twig/Tests/Node/IfTest.php | 99 + .../test/Twig/Tests/Node/ImportTest.php | 50 + .../test/Twig/Tests/Node/IncludeTest.php | 77 + .../test/Twig/Tests/Node/MacroTest.php | 62 + .../test/Twig/Tests/Node/ModuleTest.php | 170 + .../test/Twig/Tests/Node/PrintTest.php | 43 + .../test/Twig/Tests/Node/SandboxTest.php | 57 + .../Twig/Tests/Node/SandboxedModuleTest.php | 145 + .../Twig/Tests/Node/SandboxedPrintTest.php | 49 + .../test/Twig/Tests/Node/SetTest.php | 68 + .../test/Twig/Tests/Node/SpacelessTest.php | 50 + .../test/Twig/Tests/Node/TestCase.php | 40 + .../test/Twig/Tests/Node/TextTest.php | 42 + .../Twig/Tests/NodeVisitor/OptimizerTest.php | 72 + .../test/Twig/Tests/ParserTest.php | 21 + .../test/Twig/Tests/TemplateTest.php | 178 + .../test/Twig/Tests/TokenStreamTest.php | 41 + .../test/Twig/Tests/integrationTest.php | 178 + lib/Twig-1.0.0-RC1/test/bootstrap.php | 13 + nano/core/api/Api.class.php | 146 + nano/core/autoloader/Autoloader.class.php | 106 + nano/core/cache/Memcached.class.php | 160 + nano/core/cli/Cli.class.php | 56 + nano/core/cli/CronBase.class.php | 14 + nano/core/cli/TaskBase.class.php | 14 + nano/core/cli/base/Base.class.php | 103 + nano/core/cli/views/footer.php | 5 + nano/core/cli/views/header.php | 19 + nano/core/config/Config.class.php | 139 + nano/core/config/PHPErrors.class.php | 66 + nano/core/crypt/MCrypt.class.php | 35 + nano/core/db/ORM.class.php | 67 + nano/core/db/core/Database.class.php | 235 + nano/core/db/core/SelectQuery.class.php | 155 + nano/core/db/om/Base.class.php | 328 + nano/core/db/om/BaseTable.class.php | 189 + nano/core/exception/CoreException.class.php | 23 + .../exception/DatabaseException.class.php | 23 + nano/core/exception/I18nException.class.php | 23 + .../exception/ValidationException.class.php | 25 + nano/core/helpers/FileSystem.class.php | 111 + nano/core/i18n/I18n.class.php | 193 + nano/core/log/Log.class.php | 221 + nano/core/page/Page.class.php | 133 + nano/core/routing/Routing.class.php | 312 + nano/core/session/Session.class.php | 134 + nano/core/test/Unit.class.php | 22 + nano/core/view/Twiglet.class.php | 48 + nano/core/view/View.class.php | 83 + nano/core/web/Request.class.php | 111 + nano/core/web/Response.class.php | 135 + nano/core/widget/Widget.class.php | 177 + plugins/SFTP/lib/SFTP.php | 113 + plugins/SSH/lib/SSH.php | 65 + plugins/SimpleImage/lib/SimpleImage.php | 85 + plugins/sfYaml/LICENSE | 19 + plugins/sfYaml/README.markdown | 15 + plugins/sfYaml/doc/00-Introduction.markdown | 143 + plugins/sfYaml/doc/01-Usage.markdown | 110 + plugins/sfYaml/doc/02-YAML.markdown | 312 + plugins/sfYaml/doc/A-License.markdown | 108 + plugins/sfYaml/lib/sfYaml.php | 135 + plugins/sfYaml/lib/sfYamlDumper.php | 60 + plugins/sfYaml/lib/sfYamlInline.php | 442 + plugins/sfYaml/lib/sfYamlParser.php | 622 + plugins/sfYaml/package.xml | 102 + .../sfYaml/test/fixtures/YtsAnchorAlias.yml | 31 + .../sfYaml/test/fixtures/YtsBasicTests.yml | 178 + .../sfYaml/test/fixtures/YtsBlockMapping.yml | 52 + .../test/fixtures/YtsDocumentSeparator.yml | 85 + .../sfYaml/test/fixtures/YtsErrorTests.yml | 26 + .../test/fixtures/YtsFlowCollections.yml | 60 + .../sfYaml/test/fixtures/YtsFoldedScalars.yml | 176 + .../test/fixtures/YtsNullsAndEmpties.yml | 45 + .../fixtures/YtsSpecificationExamples.yml | 1695 ++ .../sfYaml/test/fixtures/YtsTypeTransfers.yml | 244 + plugins/sfYaml/test/fixtures/index.yml | 16 + plugins/sfYaml/test/fixtures/sfComments.yml | 51 + plugins/sfYaml/test/fixtures/sfCompact.yml | 53 + plugins/sfYaml/test/fixtures/sfMergeKey.yml | 27 + plugins/sfYaml/test/fixtures/sfObjects.yml | 11 + plugins/sfYaml/test/fixtures/sfQuotes.yml | 33 + plugins/sfYaml/test/fixtures/sfTests.yml | 145 + plugins/sfYaml/test/prove.php | 27 + plugins/sfYaml/test/sfYamlDumperTest.php | 152 + plugins/sfYaml/test/sfYamlInlineTest.php | 147 + plugins/sfYaml/test/sfYamlParserTest.php | 91 + project/apps/back_end/Config.class.php | 27 + project/apps/back_end/Routing.class.php | 70 + project/apps/back_end/forms/form.twig | 45 + project/apps/back_end/layouts/layout.twig | 29 + project/apps/back_end/pages/add/Add.class.php | 36 + .../apps/back_end/pages/add/views/add.twig | 25 + .../apps/back_end/pages/edit/Edit.class.php | 36 + .../apps/back_end/pages/edit/views/edit.twig | 25 + .../pages/export/ModelExport.class.php | 33 + .../pages/export/views/model_export.twig | 16 + .../pages/import/ModelImport.class.php | 33 + .../pages/import/views/model_import.twig | 16 + .../apps/back_end/pages/index/Index.class.php | 34 + .../back_end/pages/index/views/index.twig | 16 + .../pages/list_view/ListView.class.php | 39 + .../pages/list_view/views/list_view.twig | 22 + .../apps/back_end/pages/login/Login.class.php | 58 + .../back_end/pages/login/views/login.twig | 79 + .../back_end/pages/logout/Logout.class.php | 50 + .../back_end/templates/PageTemplate.class.php | 52 + .../templates/WidgetTemplate.class.php | 27 + .../back_end/tests/unit/conf/bootstrap.php | 2 + .../apps/back_end/tests/unit/conf/phpunit.xml | 7 + .../apps/back_end/widgets/add/Add.class.php | 165 + .../apps/back_end/widgets/add/views/add.twig | 55 + .../back_end/widgets/delete/Delete.class.php | 32 + .../back_end/widgets/delete/views/delete.twig | 3 + .../display_list/DisplayList.class.php | 140 + .../display_list/views/display_list.twig | 98 + .../apps/back_end/widgets/edit/Edit.class.php | 221 + .../back_end/widgets/edit/views/edit.twig | 66 + .../widgets/export/ModelExport.class.php | 44 + .../widgets/export/views/model_export.twig | 15 + .../widgets/export/views/model_export_csv.php | 30 + .../back_end/widgets/filter/Filter.class.php | 99 + .../back_end/widgets/filter/views/filter.twig | 33 + .../back_end/widgets/header/Header.class.php | 61 + .../back_end/widgets/header/views/header.php | 21 + .../widgets/import/ModelImport.class.php | 91 + .../widgets/import/views/model_import.twig | 37 + .../apps/back_end/widgets/menu/Menu.class.php | 16 + .../widgets/menu/base/BaseMenu.class.php | 49 + .../back_end/widgets/menu/views/menu.twig | 30 + .../widgets/menu/views/menu_blocks.twig | 14 + .../apps/back_end/widgets/view/View.class.php | 90 + .../back_end/widgets/view/views/view.twig | 14 + project/cli/generate/DatabaseModels.class.php | 367 + project/cli/generate/NewApp.class.php | 159 + project/cli/generate/NewCron.class.php | 31 + project/cli/generate/NewTask.class.php | 31 + project/cli/generate/views/newBaseModel.php | 30 + .../cli/generate/views/newBaseModelTable.php | 31 + project/cli/generate/views/newCron.php | 23 + .../views/newExampleProjectHeaderWidget.php | 61 + .../newExampleProjectHeaderWidgetView.php | 21 + .../views/newExampleProjectIndexPage.php | 24 + .../views/newExampleProjectIndexPageView.php | 9 + .../views/newExampleProjectTemplateClass.php | 37 + .../views/newExampleProjectTwigLayout.php | 25 + project/cli/generate/views/newMap.php | 32 + project/cli/generate/views/newModel.php | 16 + project/cli/generate/views/newModelTable.php | 16 + .../cli/generate/views/newProjectConfig.php | 23 + .../cli/generate/views/newProjectRouting.php | 36 + project/cli/generate/views/newTask.php | 23 + .../cli/generate/views/newUnitBootStrap.php | 2 + project/cli/generate/views/newUnitConf.php | 7 + .../cli/generate/views/newUnitTestExample.php | 28 + project/cli/task/RunUnitTests.class.php | 41 + project/config/Config.class.php | 466 + project/config/PHPErrors.class.php | 6 + project/config/environments/Dev.class.php | 55 + project/db/om/nanophp/Auth.class.php | 16 + project/db/om/nanophp/AuthTable.class.php | 30 + project/db/om/nanophp/Language.class.php | 38 + project/db/om/nanophp/LanguageTable.class.php | 16 + project/db/om/nanophp/Session.class.php | 36 + project/db/om/nanophp/SessionTable.class.php | 41 + project/db/om/nanophp/User.class.php | 16 + project/db/om/nanophp/UserTable.class.php | 53 + project/db/om/nanophp/base/BaseAuth.class.php | 175 + .../om/nanophp/base/BaseAuthTable.class.php | 117 + .../db/om/nanophp/base/BaseLanguage.class.php | 121 + .../nanophp/base/BaseLanguageTable.class.php | 89 + .../db/om/nanophp/base/BaseSession.class.php | 175 + .../nanophp/base/BaseSessionTable.class.php | 117 + project/db/om/nanophp/base/BaseUser.class.php | 283 + .../om/nanophp/base/BaseUserTable.class.php | 173 + project/db/om/nanophp/map/Map.class.php | 50 + project/globals/layouts/layout.twig | 25 + .../page_not_found/PageNotFound.class.php | 11 + .../page_not_found/views/page_not_found.twig | 10 + project/globals/partials/footer.php | 2 + project/globals/partials/header.php | 21 + .../globals/templates/PageTemplate.class.php | 24 + .../widgets/exception/Exception.class.php | 34 + .../widgets/exception/views/exception.twig | 14 + .../widgets/loggingbar/LoggingBar.class.php | 22 + .../globals/widgets/loggingbar/views/log.php | 129 + project/lib/swift/Swift.class.php | 21 + project/lib/twig/Twig.class.php | 35 + .../lib/twig/filters/CoreFilters.class.php | 39 + project/session/Session.class.php | 84 + sql/nanophp_structure.sql | 94 + web/.htaccess | 20 + web/css/globals/admin.css | 44 + web/css/globals/default.css | 94 + .../plugins/colorbox/colorbox-default.css | 36 + .../jquery/plugins/colorbox/colorbox1.css | 62 + .../jquery/plugins/colorbox/colorbox2.css | 41 + .../jquery/plugins/colorbox/colorbox3.css | 36 + .../jquery/plugins/colorbox/colorbox4.css | 59 + .../jquery/plugins/colorbox/colorbox5.css | 50 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_0_eeeeee_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_55_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../ui-bg_highlight-soft_100_f6f6f6_1x100.png | Bin 0 -> 90 bytes .../ui-bg_highlight-soft_25_0073ea_1x100.png | Bin 0 -> 118 bytes .../ui-bg_highlight-soft_50_dddddd_1x100.png | Bin 0 -> 92 bytes .../flick/images/ui-icons_0073ea_256x240.png | Bin 0 -> 4369 bytes .../flick/images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes .../flick/images/ui-icons_666666_256x240.png | Bin 0 -> 5355 bytes .../flick/images/ui-icons_ff0084_256x240.png | Bin 0 -> 4369 bytes .../flick/images/ui-icons_ffffff_256x240.png | Bin 0 -> 4369 bytes .../ui/flick/jquery-ui-1.8.5.custom.css | 572 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_0_f4ff42_40x100.png | Bin 0 -> 212 bytes .../images/ui-bg_flat_0_ff3387_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_80_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_75_00bbeb_1x400.png | Bin 0 -> 184 bytes .../images/ui-bg_glass_75_29d4ff_1x400.png | Bin 0 -> 175 bytes .../images/ui-bg_glass_75_8cff0f_1x400.png | Bin 0 -> 138 bytes .../ui-bg_highlight-soft_75_1c1c1c_1x100.png | Bin 0 -> 172 bytes .../images/ui-icons_000000_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_1c1c1c_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 4369 bytes .../ui/nanophp/jquery-ui-1.8.5.custom.css | 572 + .../ui-bg_diagonal-maze_20_6e4f1c_10x10.png | Bin 0 -> 154 bytes .../ui-bg_diagonal-maze_40_000000_10x10.png | Bin 0 -> 132 bytes .../ui-bg_fine-grain_10_eceadf_60x60.png | Bin 0 -> 4429 bytes .../ui-bg_fine-grain_10_f8f7f6_60x60.png | Bin 0 -> 3149 bytes .../ui-bg_fine-grain_15_eceadf_60x60.png | Bin 0 -> 4391 bytes .../ui-bg_fine-grain_15_f7f3de_60x60.png | Bin 0 -> 4645 bytes .../ui-bg_fine-grain_15_ffffff_60x60.png | Bin 0 -> 3716 bytes .../ui-bg_fine-grain_65_654b24_60x60.png | Bin 0 -> 7417 bytes .../ui-bg_fine-grain_68_b83400_60x60.png | Bin 0 -> 8306 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_3572ac_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_8c291d_256x240.png | Bin 0 -> 5355 bytes .../images/ui-icons_b83400_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_fbdb93_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 4369 bytes .../pepper-grinder/jquery-ui-1.8.5.custom.css | 572 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 120 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 111 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 110 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 119 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 101 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../ui/smoothness/jquery-ui-1.8.5.custom.css | 572 + .../ui-bg_diagonals-thick_18_b81900_40x40.png | Bin 0 -> 260 bytes .../ui-bg_diagonals-thick_20_666666_40x40.png | Bin 0 -> 251 bytes .../images/ui-bg_flat_10_000000_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_100_f6f6f6_1x400.png | Bin 0 -> 104 bytes .../images/ui-bg_glass_100_fdf5ce_1x400.png | Bin 0 -> 125 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../ui-bg_gloss-wave_35_f6a828_500x100.png | Bin 0 -> 3762 bytes .../ui-bg_highlight-soft_100_eeeeee_1x100.png | Bin 0 -> 90 bytes .../ui-bg_highlight-soft_75_ffe45c_1x100.png | Bin 0 -> 129 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_228ef1_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ef8c08_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ffd27a_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 4369 bytes .../ui-lightness/jquery-ui-1.8.5.custom.css | 572 + web/img/globals/colorbox/style1/border.png | Bin 0 -> 112 bytes web/img/globals/colorbox/style1/controls.png | Bin 0 -> 1249 bytes .../internet_explorer/borderBottomCenter.png | Bin 0 -> 111 bytes .../internet_explorer/borderBottomLeft.png | Bin 0 -> 215 bytes .../internet_explorer/borderBottomRight.png | Bin 0 -> 217 bytes .../internet_explorer/borderMiddleLeft.png | Bin 0 -> 108 bytes .../internet_explorer/borderMiddleRight.png | Bin 0 -> 108 bytes .../internet_explorer/borderTopCenter.png | Bin 0 -> 111 bytes .../internet_explorer/borderTopLeft.png | Bin 0 -> 216 bytes .../internet_explorer/borderTopRight.png | Bin 0 -> 214 bytes web/img/globals/colorbox/style1/loading.gif | Bin 0 -> 9427 bytes .../colorbox/style1/loading_background.png | Bin 0 -> 157 bytes web/img/globals/colorbox/style1/overlay.png | Bin 0 -> 182 bytes web/img/globals/colorbox/style2/controls.png | Bin 0 -> 570 bytes web/img/globals/colorbox/style2/loading.gif | Bin 0 -> 9427 bytes web/img/globals/colorbox/style3/controls.png | Bin 0 -> 1633 bytes web/img/globals/colorbox/style3/loading.gif | Bin 0 -> 9427 bytes web/img/globals/colorbox/style4/border1.png | Bin 0 -> 896 bytes web/img/globals/colorbox/style4/border2.png | Bin 0 -> 183 bytes .../internet_explorer/borderBottomCenter.png | Bin 0 -> 153 bytes .../internet_explorer/borderBottomLeft.png | Bin 0 -> 473 bytes .../internet_explorer/borderBottomRight.png | Bin 0 -> 470 bytes .../internet_explorer/borderMiddleLeft.png | Bin 0 -> 154 bytes .../internet_explorer/borderMiddleRight.png | Bin 0 -> 148 bytes .../internet_explorer/borderTopCenter.png | Bin 0 -> 143 bytes .../internet_explorer/borderTopLeft.png | Bin 0 -> 405 bytes .../internet_explorer/borderTopRight.png | Bin 0 -> 465 bytes web/img/globals/colorbox/style4/loading.gif | Bin 0 -> 9427 bytes web/img/globals/colorbox/style5/border.png | Bin 0 -> 163 bytes web/img/globals/colorbox/style5/controls.png | Bin 0 -> 2033 bytes web/img/globals/colorbox/style5/loading.gif | Bin 0 -> 9427 bytes .../colorbox/style5/loading_background.png | Bin 0 -> 166 bytes web/img/globals/tiles/25percentblack.png | Bin 0 -> 372 bytes web/img/globals/tiles/60percentblack.png | Bin 0 -> 372 bytes web/img/globals/tiles/80percentwhite.png | Bin 0 -> 2836 bytes web/img/globals/tiles/90percentE0E0E0.png | Bin 0 -> 373 bytes web/img/globals/tiles/90percentF0F0F0.png | Bin 0 -> 373 bytes web/img/globals/tiles/90percentblack.png | Bin 0 -> 372 bytes web/img/globals/tiles/90percentwhite.png | Bin 0 -> 373 bytes web/index.php | 17 + web/js/globals/admin.js | 17 + web/js/globals/core.js | 164 + web/js/globals/jquery-ui.min.js | 778 + web/js/globals/jquery.colorbox-min.js | 2 + web/js/globals/jquery.min.js | 154 + web/js/globals/tinymce/jquery.tinymce.js | 1 + web/js/globals/tinymce/langs/en.js | 170 + web/js/globals/tinymce/license.txt | 504 + .../tinymce/plugins/advhr/css/advhr.css | 5 + .../tinymce/plugins/advhr/editor_plugin.js | 1 + .../plugins/advhr/editor_plugin_src.js | 57 + .../globals/tinymce/plugins/advhr/js/rule.js | 43 + .../tinymce/plugins/advhr/langs/en_dlg.js | 5 + web/js/globals/tinymce/plugins/advhr/rule.htm | 57 + .../tinymce/plugins/advimage/css/advimage.css | 13 + .../tinymce/plugins/advimage/editor_plugin.js | 1 + .../plugins/advimage/editor_plugin_src.js | 50 + .../tinymce/plugins/advimage/image.htm | 232 + .../tinymce/plugins/advimage/img/sample.gif | Bin 0 -> 1624 bytes .../tinymce/plugins/advimage/js/image.js | 443 + .../tinymce/plugins/advimage/langs/en_dlg.js | 43 + .../tinymce/plugins/advlink/css/advlink.css | 8 + .../tinymce/plugins/advlink/editor_plugin.js | 1 + .../plugins/advlink/editor_plugin_src.js | 61 + .../tinymce/plugins/advlink/js/advlink.js | 528 + .../tinymce/plugins/advlink/langs/en_dlg.js | 52 + .../globals/tinymce/plugins/advlink/link.htm | 333 + .../tinymce/plugins/advlist/editor_plugin.js | 1 + .../plugins/advlist/editor_plugin_src.js | 154 + .../plugins/autoresize/editor_plugin.js | 1 + .../plugins/autoresize/editor_plugin_src.js | 119 + .../tinymce/plugins/autosave/editor_plugin.js | 1 + .../plugins/autosave/editor_plugin_src.js | 422 + .../tinymce/plugins/autosave/langs/en.js | 4 + .../tinymce/plugins/bbcode/editor_plugin.js | 1 + .../plugins/bbcode/editor_plugin_src.js | 120 + .../plugins/contextmenu/editor_plugin.js | 1 + .../plugins/contextmenu/editor_plugin_src.js | 147 + .../plugins/directionality/editor_plugin.js | 1 + .../directionality/editor_plugin_src.js | 82 + .../tinymce/plugins/emotions/editor_plugin.js | 1 + .../plugins/emotions/editor_plugin_src.js | 43 + .../tinymce/plugins/emotions/emotions.htm | 40 + .../plugins/emotions/img/smiley-cool.gif | Bin 0 -> 354 bytes .../plugins/emotions/img/smiley-cry.gif | Bin 0 -> 329 bytes .../emotions/img/smiley-embarassed.gif | Bin 0 -> 331 bytes .../emotions/img/smiley-foot-in-mouth.gif | Bin 0 -> 344 bytes .../plugins/emotions/img/smiley-frown.gif | Bin 0 -> 340 bytes .../plugins/emotions/img/smiley-innocent.gif | Bin 0 -> 336 bytes .../plugins/emotions/img/smiley-kiss.gif | Bin 0 -> 338 bytes .../plugins/emotions/img/smiley-laughing.gif | Bin 0 -> 344 bytes .../emotions/img/smiley-money-mouth.gif | Bin 0 -> 321 bytes .../plugins/emotions/img/smiley-sealed.gif | Bin 0 -> 325 bytes .../plugins/emotions/img/smiley-smile.gif | Bin 0 -> 345 bytes .../plugins/emotions/img/smiley-surprised.gif | Bin 0 -> 342 bytes .../emotions/img/smiley-tongue-out.gif | Bin 0 -> 328 bytes .../plugins/emotions/img/smiley-undecided.gif | Bin 0 -> 337 bytes .../plugins/emotions/img/smiley-wink.gif | Bin 0 -> 351 bytes .../plugins/emotions/img/smiley-yell.gif | Bin 0 -> 336 bytes .../tinymce/plugins/emotions/js/emotions.js | 22 + .../tinymce/plugins/emotions/langs/en_dlg.js | 20 + .../tinymce/plugins/example/dialog.htm | 22 + .../tinymce/plugins/example/editor_plugin.js | 1 + .../plugins/example/editor_plugin_src.js | 84 + .../tinymce/plugins/example/img/example.gif | Bin 0 -> 87 bytes .../tinymce/plugins/example/js/dialog.js | 19 + .../tinymce/plugins/example/langs/en.js | 3 + .../tinymce/plugins/example/langs/en_dlg.js | 3 + .../tinymce/plugins/fullpage/css/fullpage.css | 182 + .../tinymce/plugins/fullpage/editor_plugin.js | 1 + .../plugins/fullpage/editor_plugin_src.js | 153 + .../tinymce/plugins/fullpage/fullpage.htm | 571 + .../tinymce/plugins/fullpage/js/fullpage.js | 471 + .../tinymce/plugins/fullpage/langs/en_dlg.js | 85 + .../plugins/fullscreen/editor_plugin.js | 1 + .../plugins/fullscreen/editor_plugin_src.js | 151 + .../tinymce/plugins/fullscreen/fullscreen.htm | 109 + .../tinymce/plugins/iespell/editor_plugin.js | 1 + .../plugins/iespell/editor_plugin_src.js | 54 + .../plugins/inlinepopups/editor_plugin.js | 1 + .../plugins/inlinepopups/editor_plugin_src.js | 635 + .../skins/clearlooks2/img/alert.gif | Bin 0 -> 818 bytes .../skins/clearlooks2/img/button.gif | Bin 0 -> 280 bytes .../skins/clearlooks2/img/buttons.gif | Bin 0 -> 1195 bytes .../skins/clearlooks2/img/confirm.gif | Bin 0 -> 915 bytes .../skins/clearlooks2/img/corners.gif | Bin 0 -> 911 bytes .../skins/clearlooks2/img/horizontal.gif | Bin 0 -> 769 bytes .../skins/clearlooks2/img/vertical.gif | Bin 0 -> 92 bytes .../inlinepopups/skins/clearlooks2/window.css | 100 + .../tinymce/plugins/inlinepopups/template.htm | 387 + .../plugins/insertdatetime/editor_plugin.js | 1 + .../insertdatetime/editor_plugin_src.js | 83 + .../tinymce/plugins/layer/editor_plugin.js | 1 + .../plugins/layer/editor_plugin_src.js | 212 + .../plugins/legacyoutput/editor_plugin.js | 1 + .../plugins/legacyoutput/editor_plugin_src.js | 136 + .../tinymce/plugins/media/css/content.css | 6 + .../tinymce/plugins/media/css/media.css | 16 + .../tinymce/plugins/media/editor_plugin.js | 1 + .../plugins/media/editor_plugin_src.js | 414 + .../tinymce/plugins/media/img/flash.gif | Bin 0 -> 241 bytes .../tinymce/plugins/media/img/flv_player.swf | Bin 0 -> 11668 bytes .../tinymce/plugins/media/img/quicktime.gif | Bin 0 -> 303 bytes .../tinymce/plugins/media/img/realmedia.gif | Bin 0 -> 439 bytes .../tinymce/plugins/media/img/shockwave.gif | Bin 0 -> 387 bytes .../tinymce/plugins/media/img/trans.gif | Bin 0 -> 43 bytes .../plugins/media/img/windowsmedia.gif | Bin 0 -> 415 bytes .../globals/tinymce/plugins/media/js/embed.js | 73 + .../globals/tinymce/plugins/media/js/media.js | 630 + .../tinymce/plugins/media/langs/en_dlg.js | 103 + .../globals/tinymce/plugins/media/media.htm | 817 + .../plugins/nonbreaking/editor_plugin.js | 1 + .../plugins/nonbreaking/editor_plugin_src.js | 53 + .../plugins/noneditable/editor_plugin.js | 1 + .../plugins/noneditable/editor_plugin_src.js | 90 + .../tinymce/plugins/pagebreak/css/content.css | 1 + .../plugins/pagebreak/editor_plugin.js | 1 + .../plugins/pagebreak/editor_plugin_src.js | 77 + .../plugins/pagebreak/img/pagebreak.gif | Bin 0 -> 325 bytes .../tinymce/plugins/pagebreak/img/trans.gif | Bin 0 -> 43 bytes .../tinymce/plugins/paste/editor_plugin.js | 1 + .../plugins/paste/editor_plugin_src.js | 952 ++ .../tinymce/plugins/paste/js/pastetext.js | 36 + .../tinymce/plugins/paste/js/pasteword.js | 51 + .../tinymce/plugins/paste/langs/en_dlg.js | 5 + .../tinymce/plugins/paste/pastetext.htm | 27 + .../tinymce/plugins/paste/pasteword.htm | 21 + .../tinymce/plugins/preview/editor_plugin.js | 1 + .../plugins/preview/editor_plugin_src.js | 53 + .../tinymce/plugins/preview/example.html | 28 + .../tinymce/plugins/preview/jscripts/embed.js | 73 + .../tinymce/plugins/preview/preview.html | 17 + .../tinymce/plugins/print/editor_plugin.js | 1 + .../plugins/print/editor_plugin_src.js | 34 + .../tinymce/plugins/save/editor_plugin.js | 1 + .../tinymce/plugins/save/editor_plugin_src.js | 101 + .../searchreplace/css/searchreplace.css | 6 + .../plugins/searchreplace/editor_plugin.js | 1 + .../searchreplace/editor_plugin_src.js | 57 + .../plugins/searchreplace/js/searchreplace.js | 138 + .../plugins/searchreplace/langs/en_dlg.js | 16 + .../plugins/searchreplace/searchreplace.htm | 99 + .../plugins/spellchecker/css/content.css | 1 + .../plugins/spellchecker/editor_plugin.js | 1 + .../plugins/spellchecker/editor_plugin_src.js | 417 + .../plugins/spellchecker/img/wline.gif | Bin 0 -> 46 bytes .../tinymce/plugins/style/css/props.css | 13 + .../tinymce/plugins/style/editor_plugin.js | 1 + .../plugins/style/editor_plugin_src.js | 55 + .../globals/tinymce/plugins/style/js/props.js | 641 + .../tinymce/plugins/style/langs/en_dlg.js | 63 + .../globals/tinymce/plugins/style/props.htm | 723 + .../tinymce/plugins/tabfocus/editor_plugin.js | 1 + .../plugins/tabfocus/editor_plugin_src.js | 112 + web/js/globals/tinymce/plugins/table/cell.htm | 178 + .../tinymce/plugins/table/css/cell.css | 17 + .../globals/tinymce/plugins/table/css/row.css | 25 + .../tinymce/plugins/table/css/table.css | 13 + .../tinymce/plugins/table/editor_plugin.js | 1 + .../plugins/table/editor_plugin_src.js | 1139 ++ .../globals/tinymce/plugins/table/js/cell.js | 286 + .../tinymce/plugins/table/js/merge_cells.js | 27 + .../globals/tinymce/plugins/table/js/row.js | 237 + .../globals/tinymce/plugins/table/js/table.js | 454 + .../tinymce/plugins/table/langs/en_dlg.js | 74 + .../tinymce/plugins/table/merge_cells.htm | 32 + web/js/globals/tinymce/plugins/table/row.htm | 155 + .../globals/tinymce/plugins/table/table.htm | 187 + .../tinymce/plugins/template/blank.htm | 12 + .../tinymce/plugins/template/css/template.css | 23 + .../tinymce/plugins/template/editor_plugin.js | 1 + .../plugins/template/editor_plugin_src.js | 159 + .../tinymce/plugins/template/js/template.js | 106 + .../tinymce/plugins/template/langs/en_dlg.js | 15 + .../tinymce/plugins/template/template.htm | 31 + .../plugins/visualchars/editor_plugin.js | 1 + .../plugins/visualchars/editor_plugin_src.js | 83 + .../plugins/wordcount/editor_plugin.js | 1 + .../plugins/wordcount/editor_plugin_src.js | 98 + .../tinymce/plugins/xhtmlxtras/abbr.htm | 141 + .../tinymce/plugins/xhtmlxtras/acronym.htm | 141 + .../tinymce/plugins/xhtmlxtras/attributes.htm | 148 + .../tinymce/plugins/xhtmlxtras/cite.htm | 141 + .../plugins/xhtmlxtras/css/attributes.css | 11 + .../tinymce/plugins/xhtmlxtras/css/popup.css | 9 + .../tinymce/plugins/xhtmlxtras/del.htm | 161 + .../plugins/xhtmlxtras/editor_plugin.js | 1 + .../plugins/xhtmlxtras/editor_plugin_src.js | 132 + .../tinymce/plugins/xhtmlxtras/ins.htm | 161 + .../tinymce/plugins/xhtmlxtras/js/abbr.js | 28 + .../tinymce/plugins/xhtmlxtras/js/acronym.js | 28 + .../plugins/xhtmlxtras/js/attributes.js | 126 + .../tinymce/plugins/xhtmlxtras/js/cite.js | 28 + .../tinymce/plugins/xhtmlxtras/js/del.js | 63 + .../plugins/xhtmlxtras/js/element_common.js | 231 + .../tinymce/plugins/xhtmlxtras/js/ins.js | 62 + .../plugins/xhtmlxtras/langs/en_dlg.js | 32 + .../globals/tinymce/themes/advanced/about.htm | 54 + .../tinymce/themes/advanced/anchor.htm | 26 + .../tinymce/themes/advanced/charmap.htm | 52 + .../tinymce/themes/advanced/color_picker.htm | 73 + .../themes/advanced/editor_template.js | 1 + .../themes/advanced/editor_template_src.js | 1229 ++ .../globals/tinymce/themes/advanced/image.htm | 80 + .../themes/advanced/img/colorpicker.jpg | Bin 0 -> 3189 bytes .../tinymce/themes/advanced/img/icons.gif | Bin 0 -> 11794 bytes .../tinymce/themes/advanced/js/about.js | 72 + .../tinymce/themes/advanced/js/anchor.js | 37 + .../tinymce/themes/advanced/js/charmap.js | 335 + .../themes/advanced/js/color_picker.js | 253 + .../tinymce/themes/advanced/js/image.js | 245 + .../tinymce/themes/advanced/js/link.js | 156 + .../themes/advanced/js/source_editor.js | 56 + .../tinymce/themes/advanced/langs/en.js | 62 + .../tinymce/themes/advanced/langs/en_dlg.js | 51 + .../globals/tinymce/themes/advanced/link.htm | 58 + .../themes/advanced/skins/default/content.css | 36 + .../themes/advanced/skins/default/dialog.css | 117 + .../advanced/skins/default/img/buttons.png | Bin 0 -> 3274 bytes .../advanced/skins/default/img/items.gif | Bin 0 -> 70 bytes .../advanced/skins/default/img/menu_arrow.gif | Bin 0 -> 68 bytes .../advanced/skins/default/img/menu_check.gif | Bin 0 -> 70 bytes .../advanced/skins/default/img/progress.gif | Bin 0 -> 1787 bytes .../advanced/skins/default/img/tabs.gif | Bin 0 -> 1326 bytes .../themes/advanced/skins/default/ui.css | 213 + .../themes/advanced/skins/o2k7/content.css | 36 + .../themes/advanced/skins/o2k7/dialog.css | 116 + .../advanced/skins/o2k7/img/button_bg.png | Bin 0 -> 5859 bytes .../skins/o2k7/img/button_bg_black.png | Bin 0 -> 3736 bytes .../skins/o2k7/img/button_bg_silver.png | Bin 0 -> 5358 bytes .../tinymce/themes/advanced/skins/o2k7/ui.css | 215 + .../themes/advanced/skins/o2k7/ui_black.css | 8 + .../themes/advanced/skins/o2k7/ui_silver.css | 5 + .../tinymce/themes/advanced/source_editor.htm | 25 + .../tinymce/themes/simple/editor_template.js | 1 + .../themes/simple/editor_template_src.js | 85 + .../tinymce/themes/simple/img/icons.gif | Bin 0 -> 1440 bytes .../globals/tinymce/themes/simple/langs/en.js | 11 + .../themes/simple/skins/default/content.css | 25 + .../themes/simple/skins/default/ui.css | 32 + .../themes/simple/skins/o2k7/content.css | 17 + .../simple/skins/o2k7/img/button_bg.png | Bin 0 -> 5102 bytes .../tinymce/themes/simple/skins/o2k7/ui.css | 35 + web/js/globals/tinymce/tiny_mce.js | 1 + web/js/globals/tinymce/tiny_mce_popup.js | 5 + web/js/globals/tinymce/tiny_mce_src.js | 13398 ++++++++++++++++ .../globals/tinymce/utils/editable_selects.js | 70 + web/js/globals/tinymce/utils/form_utils.js | 200 + web/js/globals/tinymce/utils/mctabs.js | 77 + web/js/globals/tinymce/utils/validate.js | 220 + 1187 files changed, 139584 insertions(+) create mode 100644 LICENCE create mode 100644 README create mode 100755 cli create mode 100644 lib/Swift/CHANGES create mode 100644 lib/Swift/LICENSE create mode 100644 lib/Swift/README create mode 100644 lib/Swift/VERSION create mode 100644 lib/Swift/lib/classes/Swift.php create mode 100644 lib/Swift/lib/classes/Swift/Attachment.php create mode 100644 lib/Swift/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php create mode 100644 lib/Swift/lib/classes/Swift/ByteStream/ArrayByteStream.php create mode 100644 lib/Swift/lib/classes/Swift/ByteStream/FileByteStream.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterReader.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterReader/UsAsciiReader.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterReader/Utf8Reader.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterReaderFactory.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterStream.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterStream/ArrayCharacterStream.php create mode 100644 lib/Swift/lib/classes/Swift/CharacterStream/NgCharacterStream.php create mode 100644 lib/Swift/lib/classes/Swift/DependencyContainer.php create mode 100644 lib/Swift/lib/classes/Swift/DependencyException.php create mode 100644 lib/Swift/lib/classes/Swift/EmbeddedFile.php create mode 100644 lib/Swift/lib/classes/Swift/Encoder.php create mode 100644 lib/Swift/lib/classes/Swift/Encoder/Base64Encoder.php create mode 100644 lib/Swift/lib/classes/Swift/Encoder/QpEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Encoder/Rfc2231Encoder.php create mode 100644 lib/Swift/lib/classes/Swift/Encoding.php create mode 100644 lib/Swift/lib/classes/Swift/Events/CommandEvent.php create mode 100644 lib/Swift/lib/classes/Swift/Events/CommandListener.php create mode 100644 lib/Swift/lib/classes/Swift/Events/Event.php create mode 100644 lib/Swift/lib/classes/Swift/Events/EventDispatcher.php create mode 100644 lib/Swift/lib/classes/Swift/Events/EventListener.php create mode 100644 lib/Swift/lib/classes/Swift/Events/EventObject.php create mode 100644 lib/Swift/lib/classes/Swift/Events/ResponseEvent.php create mode 100644 lib/Swift/lib/classes/Swift/Events/ResponseListener.php create mode 100644 lib/Swift/lib/classes/Swift/Events/SendEvent.php create mode 100644 lib/Swift/lib/classes/Swift/Events/SendListener.php create mode 100644 lib/Swift/lib/classes/Swift/Events/SimpleEventDispatcher.php create mode 100644 lib/Swift/lib/classes/Swift/Events/TransportChangeEvent.php create mode 100644 lib/Swift/lib/classes/Swift/Events/TransportChangeListener.php create mode 100644 lib/Swift/lib/classes/Swift/Events/TransportExceptionEvent.php create mode 100644 lib/Swift/lib/classes/Swift/Events/TransportExceptionListener.php create mode 100644 lib/Swift/lib/classes/Swift/FailoverTransport.php create mode 100644 lib/Swift/lib/classes/Swift/FileStream.php create mode 100644 lib/Swift/lib/classes/Swift/Filterable.php create mode 100644 lib/Swift/lib/classes/Swift/Image.php create mode 100644 lib/Swift/lib/classes/Swift/InputByteStream.php create mode 100644 lib/Swift/lib/classes/Swift/IoException.php create mode 100644 lib/Swift/lib/classes/Swift/KeyCache.php create mode 100644 lib/Swift/lib/classes/Swift/KeyCache/ArrayKeyCache.php create mode 100644 lib/Swift/lib/classes/Swift/KeyCache/DiskKeyCache.php create mode 100644 lib/Swift/lib/classes/Swift/KeyCache/KeyCacheInputStream.php create mode 100644 lib/Swift/lib/classes/Swift/KeyCache/NullKeyCache.php create mode 100644 lib/Swift/lib/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php create mode 100644 lib/Swift/lib/classes/Swift/LoadBalancedTransport.php create mode 100644 lib/Swift/lib/classes/Swift/MailTransport.php create mode 100644 lib/Swift/lib/classes/Swift/Mailer.php create mode 100644 lib/Swift/lib/classes/Swift/Mailer/ArrayRecipientIterator.php create mode 100644 lib/Swift/lib/classes/Swift/Mailer/RecipientIterator.php create mode 100644 lib/Swift/lib/classes/Swift/Message.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Attachment.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/CharsetObserver.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/ContentEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/EmbeddedFile.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/EncodingObserver.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Header.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/HeaderEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/HeaderFactory.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/HeaderSet.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Headers/AbstractHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Headers/DateHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Headers/IdentificationHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Headers/MailboxHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Headers/PathHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/Message.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/MimeEntity.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/MimePart.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/ParameterizedHeader.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/SimpleHeaderFactory.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/SimpleHeaderSet.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/SimpleMessage.php create mode 100644 lib/Swift/lib/classes/Swift/Mime/SimpleMimeEntity.php create mode 100644 lib/Swift/lib/classes/Swift/MimePart.php create mode 100644 lib/Swift/lib/classes/Swift/OutputByteStream.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/AntiFloodPlugin.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Decorator/Replacements.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/DecoratorPlugin.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Logger.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/LoggerPlugin.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Loggers/EchoLogger.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Pop/Pop3Connection.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Pop/Pop3Exception.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/PopBeforeSmtpPlugin.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Reporter.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/ReporterPlugin.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Reporters/HitReporter.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Sleeper.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/ThrottlerPlugin.php create mode 100644 lib/Swift/lib/classes/Swift/Plugins/Timer.php create mode 100644 lib/Swift/lib/classes/Swift/Preferences.php create mode 100644 lib/Swift/lib/classes/Swift/ReplacementFilterFactory.php create mode 100644 lib/Swift/lib/classes/Swift/RfcComplianceException.php create mode 100644 lib/Swift/lib/classes/Swift/SendmailTransport.php create mode 100644 lib/Swift/lib/classes/Swift/SmtpTransport.php create mode 100644 lib/Swift/lib/classes/Swift/StreamFilter.php create mode 100644 lib/Swift/lib/classes/Swift/StreamFilters/ByteArrayReplacementFilter.php create mode 100644 lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilter.php create mode 100644 lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php create mode 100644 lib/Swift/lib/classes/Swift/SwiftException.php create mode 100644 lib/Swift/lib/classes/Swift/Transport.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/AbstractSmtpTransport.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/Esmtp/AuthHandler.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/Esmtp/Authenticator.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/EsmtpHandler.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/EsmtpTransport.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/FailoverTransport.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/IoBuffer.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/LoadBalancedTransport.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/MailInvoker.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/MailTransport.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/SendmailTransport.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/SimpleMailInvoker.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/SmtpAgent.php create mode 100644 lib/Swift/lib/classes/Swift/Transport/StreamBuffer.php create mode 100644 lib/Swift/lib/classes/Swift/TransportException.php create mode 100644 lib/Swift/lib/dependency_maps/cache_deps.php create mode 100644 lib/Swift/lib/dependency_maps/mime_deps.php create mode 100644 lib/Swift/lib/dependency_maps/transport_deps.php create mode 100644 lib/Swift/lib/mime_types.php create mode 100644 lib/Swift/lib/preferences.php create mode 100644 lib/Swift/lib/swift_init.php create mode 100644 lib/Swift/lib/swift_required.php create mode 100644 lib/Swift/lib/swift_required_pear.php create mode 100644 lib/Swift/test-suite/CHANGES create mode 100644 lib/Swift/test-suite/LICENSE create mode 100644 lib/Swift/test-suite/README create mode 100644 lib/Swift/test-suite/config.php create mode 100644 lib/Swift/test-suite/index.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Reporter.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Reporter/CliReporter.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Reporter/CliTestCaseReporter.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Reporter/HtmlReporter.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Reporter/HtmlTestCaseReporter.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Runner.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Runner/AbstractTestRunner.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Runner/CliRunner.php create mode 100644 lib/Swift/test-suite/lib/Sweety/Runner/HtmlRunner.php create mode 100644 lib/Swift/test-suite/lib/Sweety/TestLocator.php create mode 100644 lib/Swift/test-suite/lib/Sweety/TestLocator/PearStyleLocator.php create mode 100644 lib/Swift/test-suite/lib/simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE create mode 100644 lib/Swift/test-suite/lib/simpletest/LICENSE create mode 100644 lib/Swift/test-suite/lib/simpletest/README create mode 100644 lib/Swift/test-suite/lib/simpletest/TODO.xml create mode 100644 lib/Swift/test-suite/lib/simpletest/VERSION create mode 100644 lib/Swift/test-suite/lib/simpletest/authentication.php create mode 100644 lib/Swift/test-suite/lib/simpletest/autorun.php create mode 100644 lib/Swift/test-suite/lib/simpletest/browser.php create mode 100644 lib/Swift/test-suite/lib/simpletest/collector.php create mode 100644 lib/Swift/test-suite/lib/simpletest/compatibility.php create mode 100644 lib/Swift/test-suite/lib/simpletest/cookies.php create mode 100644 lib/Swift/test-suite/lib/simpletest/default_reporter.php create mode 100644 lib/Swift/test-suite/lib/simpletest/detached.php create mode 100644 lib/Swift/test-suite/lib/simpletest/dumper.php create mode 100644 lib/Swift/test-suite/lib/simpletest/eclipse.php create mode 100644 lib/Swift/test-suite/lib/simpletest/encoding.php create mode 100644 lib/Swift/test-suite/lib/simpletest/errors.php create mode 100644 lib/Swift/test-suite/lib/simpletest/exceptions.php create mode 100644 lib/Swift/test-suite/lib/simpletest/expectation.php create mode 100644 lib/Swift/test-suite/lib/simpletest/form.php create mode 100644 lib/Swift/test-suite/lib/simpletest/frames.php create mode 100644 lib/Swift/test-suite/lib/simpletest/http.php create mode 100644 lib/Swift/test-suite/lib/simpletest/invoker.php create mode 100644 lib/Swift/test-suite/lib/simpletest/mock_objects.php create mode 100644 lib/Swift/test-suite/lib/simpletest/page.php create mode 100644 lib/Swift/test-suite/lib/simpletest/parser.php create mode 100644 lib/Swift/test-suite/lib/simpletest/reflection_php4.php create mode 100644 lib/Swift/test-suite/lib/simpletest/reflection_php5.php create mode 100644 lib/Swift/test-suite/lib/simpletest/remote.php create mode 100644 lib/Swift/test-suite/lib/simpletest/reporter.php create mode 100644 lib/Swift/test-suite/lib/simpletest/scorer.php create mode 100644 lib/Swift/test-suite/lib/simpletest/selector.php create mode 100644 lib/Swift/test-suite/lib/simpletest/shell_tester.php create mode 100644 lib/Swift/test-suite/lib/simpletest/simpletest.php create mode 100644 lib/Swift/test-suite/lib/simpletest/socket.php create mode 100644 lib/Swift/test-suite/lib/simpletest/tag.php create mode 100644 lib/Swift/test-suite/lib/simpletest/test_case.php create mode 100644 lib/Swift/test-suite/lib/simpletest/unit_tester.php create mode 100644 lib/Swift/test-suite/lib/simpletest/url.php create mode 100644 lib/Swift/test-suite/lib/simpletest/user_agent.php create mode 100644 lib/Swift/test-suite/lib/simpletest/web_tester.php create mode 100644 lib/Swift/test-suite/lib/simpletest/xml.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Action.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/CallbackAction.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnReferenceAction.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnValueAction.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ThrowAction.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Description.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/ExpectationProvider.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AbstractExpectation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtLeastExpectation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtMostExpectation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/BetweenExpectation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/ExactlyExpectation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Invocation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationHandler.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationProxy.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationRecorder.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/AnyMatcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/BoundsMatcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/EqualMatcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/IdenticalMatcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/OptionalMatcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/PatternMatcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/ReferenceMatcher.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/MockGenerator.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/MockObject.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Mockery.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/NotSatisfiedException.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/SelfDescribing.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/Sequence.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleDescription.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleInvocation.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleSequence.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleState.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleStatePredicate.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/State.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/StateMachine.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/StatePredicate.php create mode 100644 lib/Swift/test-suite/lib/yaymock/classes/Yay/States.php create mode 100644 lib/Swift/test-suite/lib/yaymock/mock.tpl.php create mode 100644 lib/Swift/test-suite/lib/yaymock/yay_convenience.php create mode 100644 lib/Swift/test-suite/lib/yaymock/yay_mock.php create mode 100644 lib/Swift/test-suite/run.php create mode 100644 lib/Swift/test-suite/sweety.js create mode 100644 lib/Swift/test-suite/templates/sweety/css/main.css create mode 100644 lib/Swift/test-suite/templates/sweety/images/darr.gif create mode 100644 lib/Swift/test-suite/templates/sweety/images/group.gif create mode 100644 lib/Swift/test-suite/templates/sweety/images/htmlicon.gif create mode 100644 lib/Swift/test-suite/templates/sweety/images/loading.gif create mode 100644 lib/Swift/test-suite/templates/sweety/images/network.gif create mode 100644 lib/Swift/test-suite/templates/sweety/images/rarr.gif create mode 100644 lib/Swift/test-suite/templates/sweety/images/runicon.gif create mode 100644 lib/Swift/test-suite/templates/sweety/images/xmlicon.gif create mode 100644 lib/Swift/test-suite/templates/sweety/js/sweety-template.js create mode 100644 lib/Swift/test-suite/templates/sweety/suite-ui-noajax.tpl.php create mode 100644 lib/Swift/test-suite/templates/sweety/suite-ui.tpl.php create mode 100644 lib/Swift/test-suite/xpath-legacy.js create mode 100644 lib/Swift/tests/_samples/charsets/iso-8859-1/one.txt create mode 100644 lib/Swift/tests/_samples/charsets/utf-8/one.txt create mode 100644 lib/Swift/tests/_samples/charsets/utf-8/three.txt create mode 100644 lib/Swift/tests/_samples/charsets/utf-8/two.txt create mode 100644 lib/Swift/tests/_samples/files/data.txt create mode 100644 lib/Swift/tests/_samples/files/textfile.zip create mode 100644 lib/Swift/tests/acceptance.conf.php create mode 100644 lib/Swift/tests/acceptance/Swift/AttachmentAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/ByteStream/FileByteStreamAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Encoder/Base64EncoderAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/EncodingAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/MessageAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/MimePartAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php create mode 100644 lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php create mode 100644 lib/Swift/tests/bug/Swift/Bug118Test.php create mode 100644 lib/Swift/tests/bug/Swift/Bug34Test.php create mode 100644 lib/Swift/tests/bug/Swift/Bug35Test.php create mode 100644 lib/Swift/tests/bug/Swift/Bug38Test.php create mode 100644 lib/Swift/tests/bug/Swift/Bug51Test.php create mode 100644 lib/Swift/tests/bug/Swift/Bug71Test.php create mode 100644 lib/Swift/tests/bug/Swift/Bug76Test.php create mode 100644 lib/Swift/tests/helpers/Swift/Tests/IdenticalBinaryExpectation.php create mode 100644 lib/Swift/tests/helpers/Swift/Tests/SwiftSmokeTestCase.php create mode 100644 lib/Swift/tests/helpers/Swift/Tests/SwiftUnitTestCase.php create mode 100644 lib/Swift/tests/smoke.conf.php create mode 100644 lib/Swift/tests/smoke/Swift/Smoke/AttachmentSmokeTest.php create mode 100644 lib/Swift/tests/smoke/Swift/Smoke/BasicSmokeTest.php create mode 100644 lib/Swift/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php create mode 100644 lib/Swift/tests/smoke/Swift/Smoke/InternationalSmokeTest.php create mode 100644 lib/Swift/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php create mode 100644 lib/Swift/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php create mode 100644 lib/Swift/tests/unit/Swift/DependencyContainerTest.php create mode 100644 lib/Swift/tests/unit/Swift/Encoder/Base64EncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Encoder/QpEncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Events/CommandEventTest.php create mode 100644 lib/Swift/tests/unit/Swift/Events/EventObjectTest.php create mode 100644 lib/Swift/tests/unit/Swift/Events/ResponseEventTest.php create mode 100644 lib/Swift/tests/unit/Swift/Events/SendEventTest.php create mode 100644 lib/Swift/tests/unit/Swift/Events/SimpleEventDispatcherTest.php create mode 100644 lib/Swift/tests/unit/Swift/Events/TransportChangeEventTest.php create mode 100644 lib/Swift/tests/unit/Swift/Events/TransportExceptionEventTest.php create mode 100644 lib/Swift/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php create mode 100644 lib/Swift/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php create mode 100644 lib/Swift/tests/unit/Swift/MailerTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/AbstractMimeEntityTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/AttachmentTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/EmbeddedFileTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/Headers/DateHeaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/Headers/PathHeaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/MimePartTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/SimpleHeaderSetTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/SimpleMessageTest.php create mode 100644 lib/Swift/tests/unit/Swift/Mime/SimpleMimeEntityTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/AntiFloodPluginTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/DecoratorPluginTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/LoggerPluginTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/ReporterPluginTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php create mode 100644 lib/Swift/tests/unit/Swift/Plugins/ThrottlerPluginTest.php create mode 100644 lib/Swift/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php create mode 100644 lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php create mode 100644 lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/AbstractSmtpTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/EsmtpTransportTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/FailoverTransportTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/LoadBalancedTransportTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/MailTransportTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/SendmailTransportTest.php create mode 100644 lib/Swift/tests/unit/Swift/Transport/StreamBufferTest.php create mode 100755 lib/Twig-1.0.0-RC1/AUTHORS create mode 100755 lib/Twig-1.0.0-RC1/CHANGELOG create mode 100755 lib/Twig-1.0.0-RC1/LICENSE create mode 100755 lib/Twig-1.0.0-RC1/README.markdown create mode 100755 lib/Twig-1.0.0-RC1/bin/create_pear_package.php create mode 100755 lib/Twig-1.0.0-RC1/doc/advanced.rst create mode 100755 lib/Twig-1.0.0-RC1/doc/api.rst create mode 100755 lib/Twig-1.0.0-RC1/doc/extensions.rst create mode 100755 lib/Twig-1.0.0-RC1/doc/hacking.rst create mode 100755 lib/Twig-1.0.0-RC1/doc/index.rst create mode 100755 lib/Twig-1.0.0-RC1/doc/intro.rst create mode 100755 lib/Twig-1.0.0-RC1/doc/recipes.rst create mode 100755 lib/Twig-1.0.0-RC1/doc/templates.rst create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Autoloader.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Compiler.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/CompilerInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Environment.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Error.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Error/Loader.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Error/Runtime.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Error/Syntax.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/ExpressionParser.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Extension.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Extension/Core.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Extension/Escaper.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Extension/Optimizer.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Extension/Sandbox.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/ExtensionInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Filter.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Filter/Function.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Filter/Method.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/FilterInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Function.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Function/Function.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Function/Method.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/FunctionInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Lexer.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/LexerInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Loader/Array.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Loader/Filesystem.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Loader/String.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/LoaderInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Markup.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/AutoEscape.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Block.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/BlockReference.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Array.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/AssignName.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Add.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/And.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Concat.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Div.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Equal.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/FloorDiv.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Greater.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/GreaterEqual.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/In.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Less.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/LessEqual.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mod.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mul.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotEqual.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotIn.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Or.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Power.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Range.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Sub.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/BlockReference.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Conditional.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Constant.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/ExtensionReference.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Filter.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Function.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/GetAttr.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Name.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Parent.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Test.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Neg.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Not.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Pos.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/For.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/If.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Import.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Include.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Macro.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Module.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Print.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Sandbox.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedModule.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedPrint.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Set.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Spaceless.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Node/Text.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeOutputInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeTraverser.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Escaper.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Optimizer.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/SafeAnalysis.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Sandbox.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitorInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Parser.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/ParserInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityError.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicy.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicyInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Template.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TemplateInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Test/Function.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Test/Method.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TestInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/Token.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/AutoEscape.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Block.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Extends.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Filter.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/For.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/From.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/If.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Import.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Include.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Macro.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Sandbox.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Set.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Spaceless.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBroker.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBrokerInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenParserInterface.php create mode 100755 lib/Twig-1.0.0-RC1/lib/Twig/TokenStream.php create mode 100755 lib/Twig-1.0.0-RC1/package.xml.tpl create mode 100755 lib/Twig-1.0.0-RC1/phpunit.xml create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/AutoloaderTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/ExpressionParserTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Extension/SandboxTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/FileCachingTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array_call.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/binary.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/comparison.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/dotdot.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/grouping.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/in.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/magic_call.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/method_call.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/postfix.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/ternary_operator.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/unary.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/date.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/default.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/format.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/length.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/merge.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/sort.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/constant.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/cycle.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/functions.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/literal.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/nested.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/objects.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/type.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/block/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/multiple.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/nested.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/context.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/else.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/inner_variables.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/nested_else.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects_countable.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/recursive.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/values.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/from.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/expression.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/expression.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/only.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/template_instance.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/with_variables.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/external.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/from.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/self_import.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/raw.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/basic.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/capture.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/expression.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/spaceless/simple.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/constant.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/defined.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/empty.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/even.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/odd.test create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/LexerTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Loader/FilesystemTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/AutoEscapeTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockReferenceTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ArrayTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/AssignNameTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AddTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AndTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/DivTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ModTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/MulTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/OrTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/SubTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConditionalTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConstantTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/FilterTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/GetAttrTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/NameTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ParentTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NegTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NotTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/PosTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ForTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IfTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ImportTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IncludeTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/MacroTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ModuleTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/PrintTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedModuleTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedPrintTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SetTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SpacelessTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TestCase.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TextTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/NodeVisitor/OptimizerTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/ParserTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/TemplateTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/TokenStreamTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/Twig/Tests/integrationTest.php create mode 100755 lib/Twig-1.0.0-RC1/test/bootstrap.php create mode 100644 nano/core/api/Api.class.php create mode 100755 nano/core/autoloader/Autoloader.class.php create mode 100644 nano/core/cache/Memcached.class.php create mode 100644 nano/core/cli/Cli.class.php create mode 100644 nano/core/cli/CronBase.class.php create mode 100644 nano/core/cli/TaskBase.class.php create mode 100644 nano/core/cli/base/Base.class.php create mode 100644 nano/core/cli/views/footer.php create mode 100644 nano/core/cli/views/header.php create mode 100755 nano/core/config/Config.class.php create mode 100644 nano/core/config/PHPErrors.class.php create mode 100644 nano/core/crypt/MCrypt.class.php create mode 100644 nano/core/db/ORM.class.php create mode 100755 nano/core/db/core/Database.class.php create mode 100644 nano/core/db/core/SelectQuery.class.php create mode 100644 nano/core/db/om/Base.class.php create mode 100644 nano/core/db/om/BaseTable.class.php create mode 100644 nano/core/exception/CoreException.class.php create mode 100644 nano/core/exception/DatabaseException.class.php create mode 100644 nano/core/exception/I18nException.class.php create mode 100644 nano/core/exception/ValidationException.class.php create mode 100644 nano/core/helpers/FileSystem.class.php create mode 100644 nano/core/i18n/I18n.class.php create mode 100755 nano/core/log/Log.class.php create mode 100644 nano/core/page/Page.class.php create mode 100755 nano/core/routing/Routing.class.php create mode 100644 nano/core/session/Session.class.php create mode 100644 nano/core/test/Unit.class.php create mode 100644 nano/core/view/Twiglet.class.php create mode 100755 nano/core/view/View.class.php create mode 100644 nano/core/web/Request.class.php create mode 100644 nano/core/web/Response.class.php create mode 100644 nano/core/widget/Widget.class.php create mode 100644 plugins/SFTP/lib/SFTP.php create mode 100644 plugins/SSH/lib/SSH.php create mode 100644 plugins/SimpleImage/lib/SimpleImage.php create mode 100755 plugins/sfYaml/LICENSE create mode 100755 plugins/sfYaml/README.markdown create mode 100755 plugins/sfYaml/doc/00-Introduction.markdown create mode 100755 plugins/sfYaml/doc/01-Usage.markdown create mode 100755 plugins/sfYaml/doc/02-YAML.markdown create mode 100755 plugins/sfYaml/doc/A-License.markdown create mode 100755 plugins/sfYaml/lib/sfYaml.php create mode 100755 plugins/sfYaml/lib/sfYamlDumper.php create mode 100755 plugins/sfYaml/lib/sfYamlInline.php create mode 100755 plugins/sfYaml/lib/sfYamlParser.php create mode 100755 plugins/sfYaml/package.xml create mode 100755 plugins/sfYaml/test/fixtures/YtsAnchorAlias.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsBasicTests.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsBlockMapping.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsDocumentSeparator.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsErrorTests.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsFlowCollections.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsFoldedScalars.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsNullsAndEmpties.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsSpecificationExamples.yml create mode 100755 plugins/sfYaml/test/fixtures/YtsTypeTransfers.yml create mode 100755 plugins/sfYaml/test/fixtures/index.yml create mode 100755 plugins/sfYaml/test/fixtures/sfComments.yml create mode 100755 plugins/sfYaml/test/fixtures/sfCompact.yml create mode 100755 plugins/sfYaml/test/fixtures/sfMergeKey.yml create mode 100755 plugins/sfYaml/test/fixtures/sfObjects.yml create mode 100755 plugins/sfYaml/test/fixtures/sfQuotes.yml create mode 100755 plugins/sfYaml/test/fixtures/sfTests.yml create mode 100755 plugins/sfYaml/test/prove.php create mode 100755 plugins/sfYaml/test/sfYamlDumperTest.php create mode 100755 plugins/sfYaml/test/sfYamlInlineTest.php create mode 100755 plugins/sfYaml/test/sfYamlParserTest.php create mode 100644 project/apps/back_end/Config.class.php create mode 100644 project/apps/back_end/Routing.class.php create mode 100644 project/apps/back_end/forms/form.twig create mode 100644 project/apps/back_end/layouts/layout.twig create mode 100644 project/apps/back_end/pages/add/Add.class.php create mode 100644 project/apps/back_end/pages/add/views/add.twig create mode 100644 project/apps/back_end/pages/edit/Edit.class.php create mode 100644 project/apps/back_end/pages/edit/views/edit.twig create mode 100644 project/apps/back_end/pages/export/ModelExport.class.php create mode 100644 project/apps/back_end/pages/export/views/model_export.twig create mode 100644 project/apps/back_end/pages/import/ModelImport.class.php create mode 100644 project/apps/back_end/pages/import/views/model_import.twig create mode 100644 project/apps/back_end/pages/index/Index.class.php create mode 100644 project/apps/back_end/pages/index/views/index.twig create mode 100644 project/apps/back_end/pages/list_view/ListView.class.php create mode 100644 project/apps/back_end/pages/list_view/views/list_view.twig create mode 100644 project/apps/back_end/pages/login/Login.class.php create mode 100644 project/apps/back_end/pages/login/views/login.twig create mode 100644 project/apps/back_end/pages/logout/Logout.class.php create mode 100644 project/apps/back_end/templates/PageTemplate.class.php create mode 100644 project/apps/back_end/templates/WidgetTemplate.class.php create mode 100644 project/apps/back_end/tests/unit/conf/bootstrap.php create mode 100644 project/apps/back_end/tests/unit/conf/phpunit.xml create mode 100644 project/apps/back_end/widgets/add/Add.class.php create mode 100644 project/apps/back_end/widgets/add/views/add.twig create mode 100644 project/apps/back_end/widgets/delete/Delete.class.php create mode 100644 project/apps/back_end/widgets/delete/views/delete.twig create mode 100644 project/apps/back_end/widgets/display_list/DisplayList.class.php create mode 100644 project/apps/back_end/widgets/display_list/views/display_list.twig create mode 100644 project/apps/back_end/widgets/edit/Edit.class.php create mode 100644 project/apps/back_end/widgets/edit/views/edit.twig create mode 100644 project/apps/back_end/widgets/export/ModelExport.class.php create mode 100644 project/apps/back_end/widgets/export/views/model_export.twig create mode 100644 project/apps/back_end/widgets/export/views/model_export_csv.php create mode 100644 project/apps/back_end/widgets/filter/Filter.class.php create mode 100644 project/apps/back_end/widgets/filter/views/filter.twig create mode 100644 project/apps/back_end/widgets/header/Header.class.php create mode 100644 project/apps/back_end/widgets/header/views/header.php create mode 100644 project/apps/back_end/widgets/import/ModelImport.class.php create mode 100644 project/apps/back_end/widgets/import/views/model_import.twig create mode 100644 project/apps/back_end/widgets/menu/Menu.class.php create mode 100644 project/apps/back_end/widgets/menu/base/BaseMenu.class.php create mode 100644 project/apps/back_end/widgets/menu/views/menu.twig create mode 100644 project/apps/back_end/widgets/menu/views/menu_blocks.twig create mode 100644 project/apps/back_end/widgets/view/View.class.php create mode 100644 project/apps/back_end/widgets/view/views/view.twig create mode 100644 project/cli/generate/DatabaseModels.class.php create mode 100644 project/cli/generate/NewApp.class.php create mode 100644 project/cli/generate/NewCron.class.php create mode 100644 project/cli/generate/NewTask.class.php create mode 100644 project/cli/generate/views/newBaseModel.php create mode 100644 project/cli/generate/views/newBaseModelTable.php create mode 100644 project/cli/generate/views/newCron.php create mode 100644 project/cli/generate/views/newExampleProjectHeaderWidget.php create mode 100644 project/cli/generate/views/newExampleProjectHeaderWidgetView.php create mode 100644 project/cli/generate/views/newExampleProjectIndexPage.php create mode 100644 project/cli/generate/views/newExampleProjectIndexPageView.php create mode 100644 project/cli/generate/views/newExampleProjectTemplateClass.php create mode 100644 project/cli/generate/views/newExampleProjectTwigLayout.php create mode 100644 project/cli/generate/views/newMap.php create mode 100644 project/cli/generate/views/newModel.php create mode 100644 project/cli/generate/views/newModelTable.php create mode 100644 project/cli/generate/views/newProjectConfig.php create mode 100644 project/cli/generate/views/newProjectRouting.php create mode 100644 project/cli/generate/views/newTask.php create mode 100644 project/cli/generate/views/newUnitBootStrap.php create mode 100644 project/cli/generate/views/newUnitConf.php create mode 100644 project/cli/generate/views/newUnitTestExample.php create mode 100644 project/cli/task/RunUnitTests.class.php create mode 100755 project/config/Config.class.php create mode 100644 project/config/PHPErrors.class.php create mode 100644 project/config/environments/Dev.class.php create mode 100644 project/db/om/nanophp/Auth.class.php create mode 100644 project/db/om/nanophp/AuthTable.class.php create mode 100644 project/db/om/nanophp/Language.class.php create mode 100644 project/db/om/nanophp/LanguageTable.class.php create mode 100644 project/db/om/nanophp/Session.class.php create mode 100644 project/db/om/nanophp/SessionTable.class.php create mode 100644 project/db/om/nanophp/User.class.php create mode 100644 project/db/om/nanophp/UserTable.class.php create mode 100644 project/db/om/nanophp/base/BaseAuth.class.php create mode 100644 project/db/om/nanophp/base/BaseAuthTable.class.php create mode 100644 project/db/om/nanophp/base/BaseLanguage.class.php create mode 100644 project/db/om/nanophp/base/BaseLanguageTable.class.php create mode 100644 project/db/om/nanophp/base/BaseSession.class.php create mode 100644 project/db/om/nanophp/base/BaseSessionTable.class.php create mode 100644 project/db/om/nanophp/base/BaseUser.class.php create mode 100644 project/db/om/nanophp/base/BaseUserTable.class.php create mode 100644 project/db/om/nanophp/map/Map.class.php create mode 100644 project/globals/layouts/layout.twig create mode 100755 project/globals/pages/page_not_found/PageNotFound.class.php create mode 100644 project/globals/pages/page_not_found/views/page_not_found.twig create mode 100644 project/globals/partials/footer.php create mode 100644 project/globals/partials/header.php create mode 100644 project/globals/templates/PageTemplate.class.php create mode 100644 project/globals/widgets/exception/Exception.class.php create mode 100644 project/globals/widgets/exception/views/exception.twig create mode 100644 project/globals/widgets/loggingbar/LoggingBar.class.php create mode 100644 project/globals/widgets/loggingbar/views/log.php create mode 100644 project/lib/swift/Swift.class.php create mode 100644 project/lib/twig/Twig.class.php create mode 100644 project/lib/twig/filters/CoreFilters.class.php create mode 100644 project/session/Session.class.php create mode 100644 sql/nanophp_structure.sql create mode 100644 web/.htaccess create mode 100644 web/css/globals/admin.css create mode 100644 web/css/globals/default.css create mode 100755 web/css/globals/jquery/plugins/colorbox/colorbox-default.css create mode 100755 web/css/globals/jquery/plugins/colorbox/colorbox1.css create mode 100755 web/css/globals/jquery/plugins/colorbox/colorbox2.css create mode 100755 web/css/globals/jquery/plugins/colorbox/colorbox3.css create mode 100755 web/css/globals/jquery/plugins/colorbox/colorbox4.css create mode 100755 web/css/globals/jquery/plugins/colorbox/colorbox5.css create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_0_eeeeee_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_55_ffffff_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_75_ffffff_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_glass_65_ffffff_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_25_0073ea_1x100.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_50_dddddd_1x100.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-icons_0073ea_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-icons_454545_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-icons_666666_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-icons_ff0084_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/images/ui-icons_ffffff_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/flick/jquery-ui-1.8.5.custom.css create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_f4ff42_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_ff3387_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_80_ffffff_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_00bbeb_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_29d4ff_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_8cff0f_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_highlight-soft_75_1c1c1c_1x100.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_000000_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_1c1c1c_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_ffffff_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/nanophp/jquery-ui-1.8.5.custom.css create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_diagonal-maze_40_000000_10x10.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_10_eceadf_60x60.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_10_f8f7f6_60x60.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_15_eceadf_60x60.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_15_f7f3de_60x60.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_15_ffffff_60x60.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_65_654b24_60x60.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_68_b83400_60x60.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_222222_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_3572ac_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_8c291d_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_b83400_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_fbdb93_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_ffffff_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/pepper-grinder/jquery-ui-1.8.5.custom.css create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_flat_75_ffffff_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_65_ffffff_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_75_dadada_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_222222_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_2e83ff_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_454545_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_888888_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_cd0a0a_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/smoothness/jquery-ui-1.8.5.custom.css create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_flat_10_000000_40x100.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_222222_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_228ef1_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_ef8c08_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_ffd27a_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_ffffff_256x240.png create mode 100755 web/css/globals/jquery/plugins/ui/ui-lightness/jquery-ui-1.8.5.custom.css create mode 100755 web/img/globals/colorbox/style1/border.png create mode 100755 web/img/globals/colorbox/style1/controls.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderBottomCenter.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderBottomLeft.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderBottomRight.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderMiddleLeft.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderMiddleRight.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderTopCenter.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderTopLeft.png create mode 100755 web/img/globals/colorbox/style1/internet_explorer/borderTopRight.png create mode 100755 web/img/globals/colorbox/style1/loading.gif create mode 100755 web/img/globals/colorbox/style1/loading_background.png create mode 100755 web/img/globals/colorbox/style1/overlay.png create mode 100755 web/img/globals/colorbox/style2/controls.png create mode 100755 web/img/globals/colorbox/style2/loading.gif create mode 100755 web/img/globals/colorbox/style3/controls.png create mode 100755 web/img/globals/colorbox/style3/loading.gif create mode 100755 web/img/globals/colorbox/style4/border1.png create mode 100755 web/img/globals/colorbox/style4/border2.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderBottomCenter.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderBottomLeft.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderBottomRight.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderMiddleLeft.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderMiddleRight.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderTopCenter.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderTopLeft.png create mode 100755 web/img/globals/colorbox/style4/internet_explorer/borderTopRight.png create mode 100755 web/img/globals/colorbox/style4/loading.gif create mode 100755 web/img/globals/colorbox/style5/border.png create mode 100755 web/img/globals/colorbox/style5/controls.png create mode 100755 web/img/globals/colorbox/style5/loading.gif create mode 100755 web/img/globals/colorbox/style5/loading_background.png create mode 100755 web/img/globals/tiles/25percentblack.png create mode 100755 web/img/globals/tiles/60percentblack.png create mode 100755 web/img/globals/tiles/80percentwhite.png create mode 100755 web/img/globals/tiles/90percentE0E0E0.png create mode 100755 web/img/globals/tiles/90percentF0F0F0.png create mode 100755 web/img/globals/tiles/90percentblack.png create mode 100755 web/img/globals/tiles/90percentwhite.png create mode 100755 web/index.php create mode 100644 web/js/globals/admin.js create mode 100644 web/js/globals/core.js create mode 100755 web/js/globals/jquery-ui.min.js create mode 100755 web/js/globals/jquery.colorbox-min.js create mode 100755 web/js/globals/jquery.min.js create mode 100644 web/js/globals/tinymce/jquery.tinymce.js create mode 100644 web/js/globals/tinymce/langs/en.js create mode 100644 web/js/globals/tinymce/license.txt create mode 100644 web/js/globals/tinymce/plugins/advhr/css/advhr.css create mode 100644 web/js/globals/tinymce/plugins/advhr/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/advhr/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/advhr/js/rule.js create mode 100644 web/js/globals/tinymce/plugins/advhr/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/advhr/rule.htm create mode 100644 web/js/globals/tinymce/plugins/advimage/css/advimage.css create mode 100644 web/js/globals/tinymce/plugins/advimage/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/advimage/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/advimage/image.htm create mode 100644 web/js/globals/tinymce/plugins/advimage/img/sample.gif create mode 100644 web/js/globals/tinymce/plugins/advimage/js/image.js create mode 100644 web/js/globals/tinymce/plugins/advimage/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/advlink/css/advlink.css create mode 100644 web/js/globals/tinymce/plugins/advlink/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/advlink/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/advlink/js/advlink.js create mode 100644 web/js/globals/tinymce/plugins/advlink/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/advlink/link.htm create mode 100644 web/js/globals/tinymce/plugins/advlist/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/advlist/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/autoresize/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/autoresize/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/autosave/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/autosave/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/autosave/langs/en.js create mode 100644 web/js/globals/tinymce/plugins/bbcode/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/bbcode/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/contextmenu/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/contextmenu/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/directionality/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/directionality/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/emotions/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/emotions/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/emotions/emotions.htm create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-cool.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-cry.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-embarassed.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-foot-in-mouth.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-frown.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-innocent.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-kiss.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-laughing.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-money-mouth.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-sealed.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-smile.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-surprised.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-tongue-out.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-undecided.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-wink.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/img/smiley-yell.gif create mode 100644 web/js/globals/tinymce/plugins/emotions/js/emotions.js create mode 100644 web/js/globals/tinymce/plugins/emotions/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/example/dialog.htm create mode 100644 web/js/globals/tinymce/plugins/example/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/example/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/example/img/example.gif create mode 100644 web/js/globals/tinymce/plugins/example/js/dialog.js create mode 100644 web/js/globals/tinymce/plugins/example/langs/en.js create mode 100644 web/js/globals/tinymce/plugins/example/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/fullpage/css/fullpage.css create mode 100644 web/js/globals/tinymce/plugins/fullpage/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/fullpage/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/fullpage/fullpage.htm create mode 100644 web/js/globals/tinymce/plugins/fullpage/js/fullpage.js create mode 100644 web/js/globals/tinymce/plugins/fullpage/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/fullscreen/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/fullscreen/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/fullscreen/fullscreen.htm create mode 100644 web/js/globals/tinymce/plugins/iespell/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/iespell/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/button.gif create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/buttons.gif create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/horizontal.gif create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/window.css create mode 100644 web/js/globals/tinymce/plugins/inlinepopups/template.htm create mode 100644 web/js/globals/tinymce/plugins/insertdatetime/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/insertdatetime/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/layer/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/layer/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/legacyoutput/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/legacyoutput/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/media/css/content.css create mode 100644 web/js/globals/tinymce/plugins/media/css/media.css create mode 100644 web/js/globals/tinymce/plugins/media/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/media/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/media/img/flash.gif create mode 100644 web/js/globals/tinymce/plugins/media/img/flv_player.swf create mode 100644 web/js/globals/tinymce/plugins/media/img/quicktime.gif create mode 100644 web/js/globals/tinymce/plugins/media/img/realmedia.gif create mode 100644 web/js/globals/tinymce/plugins/media/img/shockwave.gif create mode 100644 web/js/globals/tinymce/plugins/media/img/trans.gif create mode 100644 web/js/globals/tinymce/plugins/media/img/windowsmedia.gif create mode 100644 web/js/globals/tinymce/plugins/media/js/embed.js create mode 100644 web/js/globals/tinymce/plugins/media/js/media.js create mode 100644 web/js/globals/tinymce/plugins/media/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/media/media.htm create mode 100644 web/js/globals/tinymce/plugins/nonbreaking/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/nonbreaking/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/noneditable/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/noneditable/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/pagebreak/css/content.css create mode 100644 web/js/globals/tinymce/plugins/pagebreak/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/pagebreak/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/pagebreak/img/pagebreak.gif create mode 100644 web/js/globals/tinymce/plugins/pagebreak/img/trans.gif create mode 100644 web/js/globals/tinymce/plugins/paste/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/paste/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/paste/js/pastetext.js create mode 100644 web/js/globals/tinymce/plugins/paste/js/pasteword.js create mode 100644 web/js/globals/tinymce/plugins/paste/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/paste/pastetext.htm create mode 100644 web/js/globals/tinymce/plugins/paste/pasteword.htm create mode 100644 web/js/globals/tinymce/plugins/preview/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/preview/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/preview/example.html create mode 100644 web/js/globals/tinymce/plugins/preview/jscripts/embed.js create mode 100644 web/js/globals/tinymce/plugins/preview/preview.html create mode 100644 web/js/globals/tinymce/plugins/print/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/print/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/save/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/save/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/searchreplace/css/searchreplace.css create mode 100644 web/js/globals/tinymce/plugins/searchreplace/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/searchreplace/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/searchreplace/js/searchreplace.js create mode 100644 web/js/globals/tinymce/plugins/searchreplace/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/searchreplace/searchreplace.htm create mode 100644 web/js/globals/tinymce/plugins/spellchecker/css/content.css create mode 100644 web/js/globals/tinymce/plugins/spellchecker/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/spellchecker/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/spellchecker/img/wline.gif create mode 100644 web/js/globals/tinymce/plugins/style/css/props.css create mode 100644 web/js/globals/tinymce/plugins/style/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/style/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/style/js/props.js create mode 100644 web/js/globals/tinymce/plugins/style/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/style/props.htm create mode 100644 web/js/globals/tinymce/plugins/tabfocus/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/tabfocus/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/table/cell.htm create mode 100644 web/js/globals/tinymce/plugins/table/css/cell.css create mode 100644 web/js/globals/tinymce/plugins/table/css/row.css create mode 100644 web/js/globals/tinymce/plugins/table/css/table.css create mode 100644 web/js/globals/tinymce/plugins/table/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/table/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/table/js/cell.js create mode 100644 web/js/globals/tinymce/plugins/table/js/merge_cells.js create mode 100644 web/js/globals/tinymce/plugins/table/js/row.js create mode 100644 web/js/globals/tinymce/plugins/table/js/table.js create mode 100644 web/js/globals/tinymce/plugins/table/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/table/merge_cells.htm create mode 100644 web/js/globals/tinymce/plugins/table/row.htm create mode 100644 web/js/globals/tinymce/plugins/table/table.htm create mode 100644 web/js/globals/tinymce/plugins/template/blank.htm create mode 100644 web/js/globals/tinymce/plugins/template/css/template.css create mode 100644 web/js/globals/tinymce/plugins/template/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/template/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/template/js/template.js create mode 100644 web/js/globals/tinymce/plugins/template/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/plugins/template/template.htm create mode 100644 web/js/globals/tinymce/plugins/visualchars/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/visualchars/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/wordcount/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/wordcount/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/abbr.htm create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/acronym.htm create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/attributes.htm create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/cite.htm create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/css/attributes.css create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/css/popup.css create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/del.htm create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin_src.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/ins.htm create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/js/abbr.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/js/acronym.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/js/attributes.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/js/cite.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/js/del.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/js/element_common.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/js/ins.js create mode 100644 web/js/globals/tinymce/plugins/xhtmlxtras/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/themes/advanced/about.htm create mode 100644 web/js/globals/tinymce/themes/advanced/anchor.htm create mode 100644 web/js/globals/tinymce/themes/advanced/charmap.htm create mode 100644 web/js/globals/tinymce/themes/advanced/color_picker.htm create mode 100644 web/js/globals/tinymce/themes/advanced/editor_template.js create mode 100644 web/js/globals/tinymce/themes/advanced/editor_template_src.js create mode 100644 web/js/globals/tinymce/themes/advanced/image.htm create mode 100644 web/js/globals/tinymce/themes/advanced/img/colorpicker.jpg create mode 100644 web/js/globals/tinymce/themes/advanced/img/icons.gif create mode 100644 web/js/globals/tinymce/themes/advanced/js/about.js create mode 100644 web/js/globals/tinymce/themes/advanced/js/anchor.js create mode 100644 web/js/globals/tinymce/themes/advanced/js/charmap.js create mode 100644 web/js/globals/tinymce/themes/advanced/js/color_picker.js create mode 100644 web/js/globals/tinymce/themes/advanced/js/image.js create mode 100644 web/js/globals/tinymce/themes/advanced/js/link.js create mode 100644 web/js/globals/tinymce/themes/advanced/js/source_editor.js create mode 100644 web/js/globals/tinymce/themes/advanced/langs/en.js create mode 100644 web/js/globals/tinymce/themes/advanced/langs/en_dlg.js create mode 100644 web/js/globals/tinymce/themes/advanced/link.htm create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/content.css create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/dialog.css create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/img/buttons.png create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/img/items.gif create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/img/menu_arrow.gif create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/img/menu_check.gif create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/img/progress.gif create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/img/tabs.gif create mode 100644 web/js/globals/tinymce/themes/advanced/skins/default/ui.css create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/content.css create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/dialog.css create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/img/button_bg.png create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/img/button_bg_black.png create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/img/button_bg_silver.png create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/ui.css create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_black.css create mode 100644 web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_silver.css create mode 100644 web/js/globals/tinymce/themes/advanced/source_editor.htm create mode 100644 web/js/globals/tinymce/themes/simple/editor_template.js create mode 100644 web/js/globals/tinymce/themes/simple/editor_template_src.js create mode 100644 web/js/globals/tinymce/themes/simple/img/icons.gif create mode 100644 web/js/globals/tinymce/themes/simple/langs/en.js create mode 100644 web/js/globals/tinymce/themes/simple/skins/default/content.css create mode 100644 web/js/globals/tinymce/themes/simple/skins/default/ui.css create mode 100644 web/js/globals/tinymce/themes/simple/skins/o2k7/content.css create mode 100644 web/js/globals/tinymce/themes/simple/skins/o2k7/img/button_bg.png create mode 100644 web/js/globals/tinymce/themes/simple/skins/o2k7/ui.css create mode 100644 web/js/globals/tinymce/tiny_mce.js create mode 100644 web/js/globals/tinymce/tiny_mce_popup.js create mode 100644 web/js/globals/tinymce/tiny_mce_src.js create mode 100644 web/js/globals/tinymce/utils/editable_selects.js create mode 100644 web/js/globals/tinymce/utils/form_utils.js create mode 100644 web/js/globals/tinymce/utils/mctabs.js create mode 100644 web/js/globals/tinymce/utils/validate.js diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..2addf7b --- /dev/null +++ b/LICENCE @@ -0,0 +1,20 @@ +-- +Copyright (c) 2010 Christopher Beck & Alex Cipriani + +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. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..5d8e56a --- /dev/null +++ b/README @@ -0,0 +1,100 @@ +888b 888 d8888 888b 888 .d88888b. 8888888b. 888 888 8888888b. 888 888 .d8888b. +8888b 888 d88888 8888b 888 d88P" "Y88b 888 Y88b 888 888 888 Y88b 888 888 d88P Y88b +88888b 888 d88P888 88888b 888 888 888 888 888 888 888 888 888 888 888 888 +888Y88b 888 d88P 888 888Y88b 888 888 888 888 d88P 8888888888 888 d88P Y88b d88P .d88P +888 Y88b888 d88P 888 888 Y88b888 888 888 8888888P" 888 888 8888888P" Y88b d88P .od888P" +888 Y88888 d88P 888 888 Y88888 888 888 888 888 888 888 Y88o88P d88P" +888 Y8888 d8888888888 888 Y8888 Y88b. .d88P 888 888 888 888 Y888P 888" +888 Y888 d88P 888 888 Y888 "Y88888P" 888 888 888 888 Y8P 888888888 + +Quick Introduction :- + +NanoPHP has been designed for rapid development of PHP based projects. It utilises most of the new features 5.3.2+ has to offer. + +Requirements :- + +MySQL 5.1 + + +Apache 2 + - mod_rewrite enabled + - mod_headers enabled (optional) + +PHP 5.3.2 + + - MySqli Extension (PHP 5 Mysql) + - xdebug (optional - recommended) + - memcached (optional - recommended) + +Installation :- + +1.) Run the SQL code in sql/nanophp_structure.sql + +2.) Edit site/config/environments/Dev.class.php + +---------------------------------------------------------------------------------------------------------------------------- + +'databases' => array ( + 'default' => array( + 'mode' => \site\core\db\core\Database::DB_MODE_SINGLE, //.only mode currently supported + 'name' => 'nanophp', //.database name + 'servers' => array( + array( + 'host' => 'localhost', //.database hostname/ip + 'port' => '3306', //.database port + 'user' => 'root', //.database username + 'pass' => 'test' //.database password + ) + ) + ) +), + +---------------------------------------------------------------------------------------------------------------------------- + +3.) Check your details are correct to access the MySQL database. + +4.) In web/.htaccess towards the bottom there should be two access rules: + +---------------------------------------------------------------------------------------------------------------------------- + +# ####### Dev ########## +RewriteCond %{HTTP_HOST} ^www\.example\.dev$ +RewriteRule .* index.php [L,E=PROJECT_APP:front_end,E=PROJECT_ENV:Dev] +# ####################### + +# ####### Dev ########## +RewriteCond %{HTTP_HOST} ^admin\.example\.dev$ +RewriteRule .* index.php [L,E=PROJECT_APP:back_end,E=PROJECT_ENV:Dev] +# ####################### + +---------------------------------------------------------------------------------------------------------------------------- + +This tells NanoPHP, that when accessing 'www.example.dev', load the 'front_end' project and use the environment 'Dev' + +To access this project, a new rule will have to be added to your Apache Virtual Hosts (/etc/apache2): + +---------------------------------------------------------------------------------------------------------------------------- + + + ServerName example.dev + ServerAlias *.example.dev + DocumentRoot /var/www/servers/nanophp-v2 + + RewriteEngine On + + +---------------------------------------------------------------------------------------------------------------------------- + +In it's most basic form, the Virtual Host Rule should look something like the above. (Remember to restart Apache - /etc/init.d/apache2 restart) + +5.) Now generate your first project by running: + +~> ./cli generate:NewProject + +Type 'front_end'. This will create a project called 'front_end' in site/projects + +6.) You can update your database models by running: + +~> ./cli generate:DatabaseModels --environment=Dev --connection=default --database=nanophp + +7.) Visit www.example.dev (you may need to add this to your hosts file) and you should see everything is ok and running. + +8.) A basic version of the Admin panel will be available at admin.example.dev (username: chris@nanophp.org, password: testing). diff --git a/cli b/cli new file mode 100755 index 0000000..6209cd4 --- /dev/null +++ b/cli @@ -0,0 +1,16 @@ +#!/usr/bin/env php +get($key,$defaultValue); +} + +function view($path,$objs=array()) { + \site\core\view\View::load($path,$objs); +} + +$_SERVER['SCRIPTS_LOAD_FROM'] = $_SERVER['PWD'].'/'; +error_reporting(E_ALL); +require_once($_SERVER['SCRIPTS_LOAD_FROM'].'nano/core/autoloader/Autoloader.class.php'); +\nano\core\autoloader\Autoloader::register(); +new \nano\core\cli\Cli(); \ No newline at end of file diff --git a/lib/Swift/CHANGES b/lib/Swift/CHANGES new file mode 100644 index 0000000..86fdb11 --- /dev/null +++ b/lib/Swift/CHANGES @@ -0,0 +1,61 @@ +Changelog for Swift Mailer, since Version 4.x +--------------------------------------------- + +09 March 2009: 4.0.0 +-------------------- + + * Complete rewrite of Version 3.x with lots of breaking changes at the interface + level, but for the best in the long run. + * Changed Connections to Transports + * Made sending more robust (less error prone) + * Simplified Swift_Message interface (removed need for separate RecipientList) + * Improved Plugin API (better event management) + * Changed all MIME generated content to be full RFC 2822 (and friends) compliant + +11 March 2009: 4.0.1 +-------------------- + + * Fixed regression with cache clearing logic in setBody(), setEncoder() and + setCharset() + +13 March 2009: 4.0.2 +-------------------- + + * Added addTo(), addCc() etc methods. + * Allowed setTo(), setCc() etc to accept a $name parameters. + * Patched a bug in MailTransport where failed recipients were not being merged. + * Added Swift::VERSION constant + * Allowed custom autoloaders to be used + +20 March 2009: 4.0.3 +-------------------- + + * Fixed Bug where base64 encoded content could exceed 76 chars per line + * Allowed Decorator plugin to accept a custom Replacements object + +12 August 2009: 4.0.4 +-------------------- + + * Bugfixes for operating under safe mode and using the MailTransport + * Compatibility for PHP 5.3 + * Optimizations for addTo(), addCc() etc operations + * Bugfix for double-escaping issue in batch sending + +27 September 2009: 4.0.5 +------------------------ + + * Fixed a warning (#78) + * Clarified license and updated the file headers accordingly + * Added __toString() methods where toString() methods already exists + * Removed constants (SWIFT_LIB_DIRECTORY, SWIFT_MAP_DIRECTORY, SWIFT_CLASS_DIRECTORY) + * Simplified autoloading + * Added a setAuthMode() method to AuthHandler (#54) + +20 January 2010: 4.0.6 +---------------------- + + * added a PEAR package and a script to generate PEAR packages + * fixed Swift_Transport_TransportException for SMTP connection not thrown (#109) + * fixed Message-IDs are not updated properly (#118) + +-- End of Changes -- diff --git a/lib/Swift/LICENSE b/lib/Swift/LICENSE new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/lib/Swift/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/lib/Swift/README b/lib/Swift/README new file mode 100644 index 0000000..794d832 --- /dev/null +++ b/lib/Swift/README @@ -0,0 +1,30 @@ +Swift Mailer, by Chris Corbyn +----------------------------- + +Swift Mailer is a component based mailing solution for PHP 5. +It is released under the LGPL license. + +Homepage: http://swiftmailer.org +Documentation: http://swiftmailer.org/docs +Mailing List: http://groups.google.com/group/swiftmailer +Bugs: http://swiftmailer.lighthouseapp.com/ +Repository: http://github.com/swiftmailer/swiftmailer + +Swift Mailer is highly object-oriented by design and lends itself +to use in complex web application with a great deal of flexibility. + +For full details on usage, see the documentation. + +IMPORTANT: Users upgrading from version 3.x or earlier absolutely + MUST read the documentation. In short, the API is considerably + different so your old code won't "just work". + +If you'd like to make a donation, we are working on a system where +donations are taken on a per-feature-request basis via the website +with target amounts for each feature. In the meantime however you +may donate directly to the author via PayPal: + + PayPal: chris@w3style.co.uk + +Donations are certainly voluntary, but seriously, you donors are +complete legends and drive this project! :) diff --git a/lib/Swift/VERSION b/lib/Swift/VERSION new file mode 100644 index 0000000..2064f10 --- /dev/null +++ b/lib/Swift/VERSION @@ -0,0 +1 @@ +Swift-4.0.6 diff --git a/lib/Swift/lib/classes/Swift.php b/lib/Swift/lib/classes/Swift.php new file mode 100644 index 0000000..77abbbf --- /dev/null +++ b/lib/Swift/lib/classes/Swift.php @@ -0,0 +1,57 @@ +createDependenciesFor('mime.attachment') + ); + + $this->setBody($data); + $this->setFilename($filename); + if ($contentType) + { + $this->setContentType($contentType); + } + } + + /** + * Create a new Attachment. + * @param string|Swift_OutputByteStream $data + * @param string $filename + * @param string $contentType + * @return Swift_Mime_Attachment + */ + public static function newInstance($data = null, $filename = null, + $contentType = null) + { + return new self($data, $filename, $contentType); + } + + /** + * Create a new Attachment from a filesystem path. + * @param string $path + * @param string $contentType optional + * @return Swift_Mime_Attachment + */ + public static function fromPath($path, $contentType = null) + { + return self::newInstance()->setFile( + new Swift_ByteStream_FileByteStream($path), + $contentType + ); + } + +} diff --git a/lib/Swift/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php b/lib/Swift/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php new file mode 100644 index 0000000..71bc3f1 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php @@ -0,0 +1,178 @@ +_filters[$key] = $filter; + } + + /** + * Remove an already present StreamFilter based on its $key. + * @param string $key + */ + public function removeFilter($key) + { + unset($this->_filters[$key]); + } + + /** + * Writes $bytes to the end of the stream. + * @param string $bytes + * @throws Swift_IoException + */ + public function write($bytes) + { + $this->_writeBuffer .= $bytes; + foreach ($this->_filters as $filter) + { + if ($filter->shouldBuffer($this->_writeBuffer)) + { + return; + } + } + $this->_doWrite($this->_writeBuffer); + return ++$this->_sequence; + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + */ + public function commit() + { + $this->_doWrite($this->_writeBuffer); + } + + /** + * Attach $is to this stream. + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + */ + public function bind(Swift_InputByteStream $is) + { + $this->_mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->_mirrors as $k => $stream) + { + if ($is === $stream) + { + if ($this->_writeBuffer !== '') + { + $stream->write($this->_filter($this->_writeBuffer)); + } + unset($this->_mirrors[$k]); + } + } + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * @throws Swift_IoException + */ + public function flushBuffers() + { + if ($this->_writeBuffer !== '') + { + $this->_doWrite($this->_writeBuffer); + } + $this->_flush(); + + foreach ($this->_mirrors as $stream) + { + $stream->flushBuffers(); + } + } + + // -- Private methods + + /** Run $bytes through all filters */ + private function _filter($bytes) + { + foreach ($this->_filters as $filter) + { + $bytes = $filter->filter($bytes); + } + return $bytes; + } + + /** Just write the bytes to the stream */ + private function _doWrite($bytes) + { + $this->_commit($this->_filter($bytes)); + + foreach ($this->_mirrors as $stream) + { + $stream->write($bytes); + } + + $this->_writeBuffer = ''; + } + +} diff --git a/lib/Swift/lib/classes/Swift/ByteStream/ArrayByteStream.php b/lib/Swift/lib/classes/Swift/ByteStream/ArrayByteStream.php new file mode 100644 index 0000000..f918889 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/ByteStream/ArrayByteStream.php @@ -0,0 +1,190 @@ +_array = $stack; + $this->_arraySize = count($stack); + } + elseif (is_string($stack)) + { + $this->write($stack); + } + else + { + $this->_array = array(); + } + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. If less bytes exist than are requested the + * remaining bytes are given instead. If no bytes are remaining at all, boolean + * false is returned. + * @param int $length + * @return string + */ + public function read($length) + { + if ($this->_offset == $this->_arraySize) + { + return false; + } + + // Don't use array slice + $end = $length + $this->_offset; + $end = $this->_arraySize<$end + ?$this->_arraySize + :$end; + $ret = ''; + for (; $this->_offset < $end; ++$this->_offset) + { + $ret .= $this->_array[$this->_offset]; + } + return $ret; + } + + /** + * Writes $bytes to the end of the stream. + * @param string $bytes + */ + public function write($bytes) + { + $to_add = str_split($bytes); + foreach ($to_add as $value) + { + $this->_array[] = $value; + } + $this->_arraySize = count($this->_array); + + foreach ($this->_mirrors as $stream) + { + $stream->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Attach $is to this stream. + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + */ + public function bind(Swift_InputByteStream $is) + { + $this->_mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->_mirrors as $k => $stream) + { + if ($is === $stream) + { + unset($this->_mirrors[$k]); + } + } + } + + /** + * Move the internal read pointer to $byteOffset in the stream. + * @param int $byteOffset + * @return boolean + */ + public function setReadPointer($byteOffset) + { + if ($byteOffset > $this->_arraySize) + { + $byteOffset = $this->_arraySize; + } + elseif ($byteOffset < 0) + { + $byteOffset = 0; + } + + $this->_offset = $byteOffset; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + */ + public function flushBuffers() + { + $this->_offset = 0; + $this->_array = array(); + $this->_arraySize = 0; + + foreach ($this->_mirrors as $stream) + { + $stream->flushBuffers(); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/ByteStream/FileByteStream.php b/lib/Swift/lib/classes/Swift/ByteStream/FileByteStream.php new file mode 100644 index 0000000..14773c2 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/ByteStream/FileByteStream.php @@ -0,0 +1,177 @@ +_path = $path; + $this->_mode = $writable ? 'w+b' : 'rb'; + $this->_quotes = get_magic_quotes_runtime(); + } + + /** + * Get the complete path to the file. + * @return string + */ + public function getPath() + { + return $this->_path; + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. If less bytes exist than are requested the + * remaining bytes are given instead. If no bytes are remaining at all, boolean + * false is returned. + * @param int $length + * @return string + * @throws Swift_IoException + */ + public function read($length) + { + $fp = $this->_getReadHandle(); + if (!feof($fp)) + { + if ($this->_quotes) + { + set_magic_quotes_runtime(0); + } + $bytes = fread($fp, $length); + if ($this->_quotes) + { + set_magic_quotes_runtime(1); + } + $this->_offset = ftell($fp); + return $bytes; + } + else + { + return false; + } + } + + /** + * Move the internal read pointer to $byteOffset in the stream. + * @param int $byteOffset + * @return boolean + */ + public function setReadPointer($byteOffset) + { + if (isset($this->_reader)) + { + fseek($this->_reader, $byteOffset, SEEK_SET); + } + $this->_offset = $byteOffset; + } + + // -- Private methods + + /** Just write the bytes to the file */ + protected function _commit($bytes) + { + fwrite($this->_getWriteHandle(), $bytes); + $this->_resetReadHandle(); + } + + /** Not used */ + protected function _flush() + { + } + + /** Get the resource for reading */ + private function _getReadHandle() + { + if (!isset($this->_reader)) + { + if (!$this->_reader = fopen($this->_path, 'rb')) + { + throw new Swift_IoException( + 'Unable to open file for reading [' . $this->_path . ']' + ); + } + fseek($this->_reader, $this->_offset, SEEK_SET); + } + return $this->_reader; + } + + /** Get the resource for writing */ + private function _getWriteHandle() + { + if (!isset($this->_writer)) + { + if (!$this->_writer = fopen($this->_path, $this->_mode)) + { + throw new Swift_IoException( + 'Unable to open file for writing [' . $this->_path . ']' + ); + } + } + return $this->_writer; + } + + /** Force a reload of the resource for writing */ + private function _resetWriteHandle() + { + if (isset($this->_writer)) + { + fclose($this->_writer); + $this->_writer = null; + } + } + + /** Force a reload of the resource for reading */ + private function _resetReadHandle() + { + if (isset($this->_reader)) + { + fclose($this->_reader); + $this->_reader = null; + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/CharacterReader.php b/lib/Swift/lib/classes/Swift/CharacterReader.php new file mode 100644 index 0000000..53d39ec --- /dev/null +++ b/lib/Swift/lib/classes/Swift/CharacterReader.php @@ -0,0 +1,60 @@ + + */ +interface Swift_CharacterReader +{ + const MAP_TYPE_INVALID = 0x01; + const MAP_TYPE_FIXED_LEN = 0x02; + const MAP_TYPE_POSITIONS = 0x03; + + /** + * Returns the complete charactermap + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * @return int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars); + + /** + * Returns mapType + * @int mapType + */ + public function getMapType(); + + /** + * Returns an integer which specifies how many more bytes to read. + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * @param int[] $bytes + * @return int + */ + public function validateByteSequence($bytes, $size); + + /** + * Returns the number of bytes which should be read to start each character. + * For fixed width character sets this should be the number of + * octets-per-character. For multibyte character sets this will probably be 1. + * @return int + */ + public function getInitialByteSize(); + +} diff --git a/lib/Swift/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php b/lib/Swift/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php new file mode 100644 index 0000000..26b13ff --- /dev/null +++ b/lib/Swift/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php @@ -0,0 +1,96 @@ + + */ +class Swift_CharacterReader_GenericFixedWidthReader + implements Swift_CharacterReader +{ + + /** + * The number of bytes in a single character. + * @var int + * @access private + */ + private $_width; + + /** + * Creates a new GenericFixedWidthReader using $width bytes per character. + * @param int $width + */ + public function __construct($width) + { + $this->_width = $width; + } + + /** + * Returns the complete charactermap + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * @return $int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars) + { + $strlen = strlen($string); + // % and / are CPU intensive, so, maybe find a better way + $ignored = $strlen%$this->_width; + $ignoredChars = substr($string, - $ignored); + $currentMap = $this->_width; + return ($strlen - $ignored)/$this->_width; + + } + + /** + * Returns mapType + * @int mapType + */ + public function getMapType() + { + return self::MAP_TYPE_FIXED_LEN; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * @param string $bytes + * @return int + */ + public function validateByteSequence($bytes, $size) + { + $needed = $this->_width - $size; + return ($needed > -1) + ? $needed + : -1 + ; + } + + /** + * Returns the number of bytes which should be read to start each character. + * @return int + */ + public function getInitialByteSize() + { + return $this->_width; + } + +} diff --git a/lib/Swift/lib/classes/Swift/CharacterReader/UsAsciiReader.php b/lib/Swift/lib/classes/Swift/CharacterReader/UsAsciiReader.php new file mode 100644 index 0000000..3e0228a --- /dev/null +++ b/lib/Swift/lib/classes/Swift/CharacterReader/UsAsciiReader.php @@ -0,0 +1,83 @@ +"\x07F") + { // Invalid char + $currentMap[$i+$startOffset]=$string[$i]; + } + } + return $strlen; + } + + /** + * Returns mapType + * @int mapType + */ + public function getMapType() + { + return self::MAP_TYPE_INVALID; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * @param string $bytes + * @return int + */ + public function validateByteSequence($bytes, $size) + { + $byte = reset($bytes); + if (1 == count($bytes) && $byte >= 0x00 && $byte <= 0x7F) + { + return 0; + } + else + { + return -1; + } + } + + /** + * Returns the number of bytes which should be read to start each character. + * @return int + */ + public function getInitialByteSize() + { + return 1; + } + +} diff --git a/lib/Swift/lib/classes/Swift/CharacterReader/Utf8Reader.php b/lib/Swift/lib/classes/Swift/CharacterReader/Utf8Reader.php new file mode 100644 index 0000000..54ea9a4 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/CharacterReader/Utf8Reader.php @@ -0,0 +1,183 @@ + + */ +class Swift_CharacterReader_Utf8Reader + implements Swift_CharacterReader +{ + + /** Pre-computed for optimization */ + private static $length_map=array( +//N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x0N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x1N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x2N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x3N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x4N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x5N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x6N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x7N + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x8N + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x9N + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xAN + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xBN + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xCN + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xDN + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, //0xEN + 4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0 //0xFN + ); + private static $s_length_map=array( + "\x00"=>1, "\x01"=>1, "\x02"=>1, "\x03"=>1, "\x04"=>1, "\x05"=>1, "\x06"=>1, "\x07"=>1, + "\x08"=>1, "\x09"=>1, "\x0a"=>1, "\x0b"=>1, "\x0c"=>1, "\x0d"=>1, "\x0e"=>1, "\x0f"=>1, + "\x10"=>1, "\x11"=>1, "\x12"=>1, "\x13"=>1, "\x14"=>1, "\x15"=>1, "\x16"=>1, "\x17"=>1, + "\x18"=>1, "\x19"=>1, "\x1a"=>1, "\x1b"=>1, "\x1c"=>1, "\x1d"=>1, "\x1e"=>1, "\x1f"=>1, + "\x20"=>1, "\x21"=>1, "\x22"=>1, "\x23"=>1, "\x24"=>1, "\x25"=>1, "\x26"=>1, "\x27"=>1, + "\x28"=>1, "\x29"=>1, "\x2a"=>1, "\x2b"=>1, "\x2c"=>1, "\x2d"=>1, "\x2e"=>1, "\x2f"=>1, + "\x30"=>1, "\x31"=>1, "\x32"=>1, "\x33"=>1, "\x34"=>1, "\x35"=>1, "\x36"=>1, "\x37"=>1, + "\x38"=>1, "\x39"=>1, "\x3a"=>1, "\x3b"=>1, "\x3c"=>1, "\x3d"=>1, "\x3e"=>1, "\x3f"=>1, + "\x40"=>1, "\x41"=>1, "\x42"=>1, "\x43"=>1, "\x44"=>1, "\x45"=>1, "\x46"=>1, "\x47"=>1, + "\x48"=>1, "\x49"=>1, "\x4a"=>1, "\x4b"=>1, "\x4c"=>1, "\x4d"=>1, "\x4e"=>1, "\x4f"=>1, + "\x50"=>1, "\x51"=>1, "\x52"=>1, "\x53"=>1, "\x54"=>1, "\x55"=>1, "\x56"=>1, "\x57"=>1, + "\x58"=>1, "\x59"=>1, "\x5a"=>1, "\x5b"=>1, "\x5c"=>1, "\x5d"=>1, "\x5e"=>1, "\x5f"=>1, + "\x60"=>1, "\x61"=>1, "\x62"=>1, "\x63"=>1, "\x64"=>1, "\x65"=>1, "\x66"=>1, "\x67"=>1, + "\x68"=>1, "\x69"=>1, "\x6a"=>1, "\x6b"=>1, "\x6c"=>1, "\x6d"=>1, "\x6e"=>1, "\x6f"=>1, + "\x70"=>1, "\x71"=>1, "\x72"=>1, "\x73"=>1, "\x74"=>1, "\x75"=>1, "\x76"=>1, "\x77"=>1, + "\x78"=>1, "\x79"=>1, "\x7a"=>1, "\x7b"=>1, "\x7c"=>1, "\x7d"=>1, "\x7e"=>1, "\x7f"=>1, + "\x80"=>0, "\x81"=>0, "\x82"=>0, "\x83"=>0, "\x84"=>0, "\x85"=>0, "\x86"=>0, "\x87"=>0, + "\x88"=>0, "\x89"=>0, "\x8a"=>0, "\x8b"=>0, "\x8c"=>0, "\x8d"=>0, "\x8e"=>0, "\x8f"=>0, + "\x90"=>0, "\x91"=>0, "\x92"=>0, "\x93"=>0, "\x94"=>0, "\x95"=>0, "\x96"=>0, "\x97"=>0, + "\x98"=>0, "\x99"=>0, "\x9a"=>0, "\x9b"=>0, "\x9c"=>0, "\x9d"=>0, "\x9e"=>0, "\x9f"=>0, + "\xa0"=>0, "\xa1"=>0, "\xa2"=>0, "\xa3"=>0, "\xa4"=>0, "\xa5"=>0, "\xa6"=>0, "\xa7"=>0, + "\xa8"=>0, "\xa9"=>0, "\xaa"=>0, "\xab"=>0, "\xac"=>0, "\xad"=>0, "\xae"=>0, "\xaf"=>0, + "\xb0"=>0, "\xb1"=>0, "\xb2"=>0, "\xb3"=>0, "\xb4"=>0, "\xb5"=>0, "\xb6"=>0, "\xb7"=>0, + "\xb8"=>0, "\xb9"=>0, "\xba"=>0, "\xbb"=>0, "\xbc"=>0, "\xbd"=>0, "\xbe"=>0, "\xbf"=>0, + "\xc0"=>2, "\xc1"=>2, "\xc2"=>2, "\xc3"=>2, "\xc4"=>2, "\xc5"=>2, "\xc6"=>2, "\xc7"=>2, + "\xc8"=>2, "\xc9"=>2, "\xca"=>2, "\xcb"=>2, "\xcc"=>2, "\xcd"=>2, "\xce"=>2, "\xcf"=>2, + "\xd0"=>2, "\xd1"=>2, "\xd2"=>2, "\xd3"=>2, "\xd4"=>2, "\xd5"=>2, "\xd6"=>2, "\xd7"=>2, + "\xd8"=>2, "\xd9"=>2, "\xda"=>2, "\xdb"=>2, "\xdc"=>2, "\xdd"=>2, "\xde"=>2, "\xdf"=>2, + "\xe0"=>3, "\xe1"=>3, "\xe2"=>3, "\xe3"=>3, "\xe4"=>3, "\xe5"=>3, "\xe6"=>3, "\xe7"=>3, + "\xe8"=>3, "\xe9"=>3, "\xea"=>3, "\xeb"=>3, "\xec"=>3, "\xed"=>3, "\xee"=>3, "\xef"=>3, + "\xf0"=>4, "\xf1"=>4, "\xf2"=>4, "\xf3"=>4, "\xf4"=>4, "\xf5"=>4, "\xf6"=>4, "\xf7"=>4, + "\xf8"=>5, "\xf9"=>5, "\xfa"=>5, "\xfb"=>5, "\xfc"=>6, "\xfd"=>6, "\xfe"=>0, "\xff"=>0, + ); + + /** + * Returns the complete charactermap + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars) + { + if (!isset($currentMap['i']) || !isset($currentMap['p'])) + { + $currentMap['p'] = $currentMap['i'] = array(); + } + $strlen=strlen($string); + $charPos=count($currentMap['p']); + $foundChars=0; + $invalid=false; + for ($i=0; $i<$strlen; ++$i) + { + $char=$string[$i]; + $size=self::$s_length_map[$char]; + if ($size==0) + { + /* char is invalid, we must wait for a resync */ + $invalid=true; + continue; + } + else + { + if ($invalid==true) + { + /* We mark the chars as invalid and start a new char */ + $currentMap['p'][$charPos+$foundChars]=$startOffset+$i; + $currentMap['i'][$charPos+$foundChars]=true; + ++$foundChars; + $invalid=false; + } + if (($i+$size) > $strlen){ + $ignoredChars=substr($string, $i); + break; + } + for ($j=1; $j<$size; ++$j) + { + $char=$string[$i+$j]; + if ($char>"\x7F" && $char<"\xC0") + { + // Valid - continue parsing + } + else + { + /* char is invalid, we must wait for a resync */ + $invalid=true; + continue 2; + } + } + /* Ok we got a complete char here */ + $lastChar=$currentMap['p'][$charPos+$foundChars]=$startOffset+$i+$size; + $i+=$j-1; + ++$foundChars; + } + } + return $foundChars; + } + + /** + * Returns mapType + * @int mapType + */ + public function getMapType() + { + return self::MAP_TYPE_POSITIONS; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * @param string $bytes + * @return int + */ + public function validateByteSequence($bytes, $size) + { + if ($size<1){ + return -1; + } + $needed = self::$length_map[$bytes[0]] - $size; + return ($needed > -1) + ? $needed + : -1 + ; + } + + /** + * Returns the number of bytes which should be read to start each character. + * @return int + */ + public function getInitialByteSize() + { + return 1; + } + +} diff --git a/lib/Swift/lib/classes/Swift/CharacterReaderFactory.php b/lib/Swift/lib/classes/Swift/CharacterReaderFactory.php new file mode 100644 index 0000000..9e01de1 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/CharacterReaderFactory.php @@ -0,0 +1,29 @@ + $prefix . 'GenericFixedWidthReader', + 'constructor' => array(1) + ); + + $doubleByte = array( + 'class' => $prefix . 'GenericFixedWidthReader', + 'constructor' => array(2) + ); + + $fourBytes = array( + 'class' => $prefix . 'GenericFixedWidthReader', + 'constructor' => array(4) + ); + + //Utf-8 + $this->_map['utf-?8'] = array( + 'class' => $prefix . 'Utf8Reader', + 'constructor' => array() + ); + + //7-8 bit charsets + $this->_map['(us-)?ascii'] = $singleByte; + $this->_map['(iso|iec)-?8859-?[0-9]+'] = $singleByte; + $this->_map['windows-?125[0-9]'] = $singleByte; + $this->_map['cp-?[0-9]+'] = $singleByte; + $this->_map['ansi'] = $singleByte; + $this->_map['macintosh'] = $singleByte; + $this->_map['koi-?7'] = $singleByte; + $this->_map['koi-?8-?.+'] = $singleByte; + $this->_map['mik'] = $singleByte; + $this->_map['(cork|t1)'] = $singleByte; + $this->_map['v?iscii'] = $singleByte; + + //16 bits + $this->_map['(ucs-?2|utf-?16)'] = $doubleByte; + + //32 bits + $this->_map['(ucs-?4|utf-?32)'] = $fourBytes; + + //Fallback + $this->_map['.*'] = $singleByte; + } + + /** + * Returns a CharacterReader suitable for the charset applied. + * @param string $charset + * @return Swift_CharacterReader + */ + public function getReaderFor($charset) + { + $charset = trim(strtolower($charset)); + foreach ($this->_map as $pattern => $spec) + { + $re = '/^' . $pattern . '$/D'; + if (preg_match($re, $charset)) + { + if (!array_key_exists($pattern, $this->_loaded)) + { + $reflector = new ReflectionClass($spec['class']); + if ($reflector->getConstructor()) + { + $reader = $reflector->newInstanceArgs($spec['constructor']); + } + else + { + $reader = $reflector->newInstance(); + } + $this->_loaded[$pattern] = $reader; + } + return $this->_loaded[$pattern]; + } + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/CharacterStream.php b/lib/Swift/lib/classes/Swift/CharacterStream.php new file mode 100644 index 0000000..bf91528 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/CharacterStream.php @@ -0,0 +1,86 @@ +setCharacterReaderFactory($factory); + $this->setCharacterSet($charset); + } + + /** + * Set the character set used in this CharacterStream. + * @param string $charset + */ + public function setCharacterSet($charset) + { + $this->_charset = $charset; + $this->_charReader = null; + } + + /** + * Set the CharacterReaderFactory for multi charset support. + * @param Swift_CharacterReaderFactory $factory + */ + public function setCharacterReaderFactory( + Swift_CharacterReaderFactory $factory) + { + $this->_charReaderFactory = $factory; + } + + /** + * Overwrite this character stream using the byte sequence in the byte stream. + * @param Swift_OutputByteStream $os output stream to read from + */ + public function importByteStream(Swift_OutputByteStream $os) + { + if (!isset($this->_charReader)) + { + $this->_charReader = $this->_charReaderFactory + ->getReaderFor($this->_charset); + } + + $startLength = $this->_charReader->getInitialByteSize(); + while (false !== $bytes = $os->read($startLength)) + { + $c = array(); + for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) + { + $c[] = self::$_byteMap[$bytes[$i]]; + } + $size = count($c); + $need = $this->_charReader + ->validateByteSequence($c, $size); + if ($need > 0 && + false !== $bytes = $os->read($need)) + { + for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) + { + $c[] = self::$_byteMap[$bytes[$i]]; + } + } + $this->_array[] = $c; + ++$this->_array_size; + } + } + + /** + * Import a string a bytes into this CharacterStream, overwriting any existing + * data in the stream. + * @param string $string + */ + public function importString($string) + { + $this->flushContents(); + $this->write($string); + } + + /** + * Read $length characters from the stream and move the internal pointer + * $length further into the stream. + * @param int $length + * @return string + */ + public function read($length) + { + if ($this->_offset == $this->_array_size) + { + return false; + } + + // Don't use array slice + $arrays = array(); + $end = $length + $this->_offset; + for ($i = $this->_offset; $i < $end; ++$i) + { + if (!isset($this->_array[$i])) + { + break; + } + $arrays[] = $this->_array[$i]; + } + $this->_offset += $i - $this->_offset; // Limit function calls + $chars = false; + foreach ($arrays as $array) + { + $chars .= implode('', array_map('chr', $array)); + } + return $chars; + } + + /** + * Read $length characters from the stream and return a 1-dimensional array + * containing there octet values. + * @param int $length + * @return int[] + */ + public function readBytes($length) + { + if ($this->_offset == $this->_array_size) + { + return false; + } + $arrays = array(); + $end = $length + $this->_offset; + for ($i = $this->_offset; $i < $end; ++$i) + { + if (!isset($this->_array[$i])) + { + break; + } + $arrays[] = $this->_array[$i]; + } + $this->_offset += ($i - $this->_offset); // Limit function calls + return call_user_func_array('array_merge', $arrays); + } + + /** + * Write $chars to the end of the stream. + * @param string $chars + */ + public function write($chars) + { + if (!isset($this->_charReader)) + { + $this->_charReader = $this->_charReaderFactory->getReaderFor( + $this->_charset); + } + + $startLength = $this->_charReader->getInitialByteSize(); + + $fp = fopen('php://memory', 'w+b'); + fwrite($fp, $chars); + unset($chars); + fseek($fp, 0, SEEK_SET); + + $buffer = array(0); + $buf_pos = 1; + $buf_len = 1; + $has_datas = true; + do + { + $bytes = array(); + // Buffer Filing + if ($buf_len - $buf_pos < $startLength) + { + $buf = array_splice($buffer, $buf_pos); + $new = $this->_reloadBuffer($fp, 100); + if ($new) + { + $buffer = array_merge($buf, $new); + $buf_len = count($buffer); + $buf_pos = 0; + } + else + { + $has_datas = false; + } + } + if ($buf_len - $buf_pos > 0) + { + $size = 0; + for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) + { + ++$size; + $bytes[] = $buffer[$buf_pos++]; + } + $need = $this->_charReader->validateByteSequence( + $bytes, $size); + if ($need > 0) + { + if ($buf_len - $buf_pos < $need) + { + $new = $this->_reloadBuffer($fp, $need); + + if ($new) + { + $buffer = array_merge($buffer, $new); + $buf_len = count($buffer); + } + } + for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) + { + $bytes[] = $buffer[$buf_pos++]; + } + } + $this->_array[] = $bytes; + ++$this->_array_size; + } + } + while ($has_datas); + + fclose($fp); + } + + /** + * Move the internal pointer to $charOffset in the stream. + * @param int $charOffset + */ + public function setPointer($charOffset) + { + if ($charOffset > $this->_array_size) + { + $charOffset = $this->_array_size; + } + elseif ($charOffset < 0) + { + $charOffset = 0; + } + $this->_offset = $charOffset; + } + + /** + * Empty the stream and reset the internal pointer. + */ + public function flushContents() + { + $this->_offset = 0; + $this->_array = array(); + $this->_array_size = 0; + } + + private function _reloadBuffer($fp, $len) + { + if (!feof($fp) && ($bytes = fread($fp, $len)) !== false) + { + $buf = array(); + for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) + { + $buf[] = self::$_byteMap[$bytes[$i]]; + } + return $buf; + } + return false; + } + + private static function _initializeMaps() + { + if (!isset(self::$_charMap)) + { + self::$_charMap = array(); + for ($byte = 0; $byte < 256; ++$byte) + { + self::$_charMap[$byte] = chr($byte); + } + self::$_byteMap = array_flip(self::$_charMap); + } + } +} diff --git a/lib/Swift/lib/classes/Swift/CharacterStream/NgCharacterStream.php b/lib/Swift/lib/classes/Swift/CharacterStream/NgCharacterStream.php new file mode 100644 index 0000000..f090aa7 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/CharacterStream/NgCharacterStream.php @@ -0,0 +1,300 @@ +. + + */ + +//@require 'Swift/CharacterStream.php'; +//@require 'Swift/OutputByteStream.php'; + + +/** + * A CharacterStream implementation which stores characters in an internal array. + * @package Swift + * @subpackage CharacterStream + * @author Xavier De Cock + */ + +Class Swift_CharacterStream_NgCharacterStream + implements Swift_CharacterStream +{ + + /** + * The char reader (lazy-loaded) for the current charset. + * @var Swift_CharacterReader + * @access private + */ + private $_charReader; + + /** + * A factory for creatiing CharacterReader instances. + * @var Swift_CharacterReaderFactory + * @access private + */ + private $_charReaderFactory; + + /** + * The character set this stream is using. + * @var string + * @access private + */ + private $_charset; + + /** + * The datas stored as is + * + * @var string + */ + private $_datas = ""; + + /** + * Number of bytes in the stream + * + * @var int + */ + private $_datasSize = 0; + + /** + * Map + * + * @var mixed + */ + private $_map; + + /** + * Map Type + * + * @var int + */ + private $_mapType = 0; + + /** + * Number of characters in the stream + * + * @var int + */ + private $_charCount = 0; + + /** + * Position in the stream + * + * @var unknown_type + */ + private $_currentPos = 0; + + /** + * The constructor + * + * @param Swift_CharacterReaderFactory $factory + * @param unknown_type $charset + */ + public function __construct(Swift_CharacterReaderFactory $factory, + $charset) + { + $this->setCharacterReaderFactory($factory); + $this->setCharacterSet($charset); + } + + /* -- Changing parameters of the stream -- */ + + /** + * Set the character set used in this CharacterStream. + * @param string $charset + */ + public function setCharacterSet($charset) + { + $this->_charset = $charset; + $this->_charReader = null; + $this->_mapType = 0; + } + + /** + * Set the CharacterReaderFactory for multi charset support. + * @param Swift_CharacterReaderFactory $factory + */ + public function setCharacterReaderFactory( + Swift_CharacterReaderFactory $factory) + { + $this->_charReaderFactory = $factory; + } + + /** + * @see Swift_CharacterStream::flushContents() + * + */ + public function flushContents() + { + $this->_datas = null; + $this->_map = null; + $this->_charCount = 0; + $this->_currentPos = 0; + $this->_datasSize = 0; + } + + /** + * @see Swift_CharacterStream::importByteStream() + * + * @param Swift_OutputByteStream $os + */ + public function importByteStream(Swift_OutputByteStream $os) + { + $this->flushContents(); + $blocks=512; + $os->setReadPointer(0); + while(false!==($read = $os->read($blocks))) + $this->write($read); + } + + /** + * @see Swift_CharacterStream::importString() + * + * @param string $string + */ + public function importString($string) + { + $this->flushContents(); + $this->write($string); + } + + /** + * @see Swift_CharacterStream::read() + * + * @param int $length + * @return string + */ + public function read($length) + { + if ($this->_currentPos>=$this->_charCount) + { + return false; + } + $ret=false; + $length = ($this->_currentPos+$length > $this->_charCount) + ? $this->_charCount - $this->_currentPos + : $length; + switch ($this->_mapType) + { + case Swift_CharacterReader::MAP_TYPE_FIXED_LEN: + $len = $length*$this->_map; + $ret = substr($this->_datas, + $this->_currentPos * $this->_map, + $len); + $this->_currentPos += $length; + break; + + case Swift_CharacterReader::MAP_TYPE_INVALID: + $end = $this->_currentPos + $length; + $end = $end > $this->_charCount + ?$this->_charCount + :$end; + $ret = ''; + for (; $this->_currentPos < $length; ++$this->_currentPos) + { + if (isset ($this->_map[$this->_currentPos])) + { + $ret .= '?'; + } + else + { + $ret .= $this->_datas[$this->_currentPos]; + } + } + break; + + case Swift_CharacterReader::MAP_TYPE_POSITIONS: + $end = $this->_currentPos + $length; + $end = $end > $this->_charCount + ?$this->_charCount + :$end; + $ret = ''; + $start = 0; + if ($this->_currentPos>0) + { + $start = $this->_map['p'][$this->_currentPos-1]; + } + $to = $start; + for (; $this->_currentPos < $end; ++$this->_currentPos) + { + if (isset($this->_map['i'][$this->_currentPos])) { + $ret .= substr($this->_datas, $start, $to - $start).'?'; + $start = $this->_map['p'][$this->_currentPos]; + } else { + $to = $this->_map['p'][$this->_currentPos]; + } + } + $ret .= substr($this->_datas, $start, $to - $start); + break; + } + return $ret; + } + + /** + * @see Swift_CharacterStream::readBytes() + * + * @param int $length + * @return int[] + */ + public function readBytes($length) + { + $read=$this->read($length); + if ($read!==false) + { + $ret = array_map('ord', str_split($read, 1)); + return $ret; + } + return false; + } + + /** + * @see Swift_CharacterStream::setPointer() + * + * @param int $charOffset + */ + public function setPointer($charOffset) + { + if ($this->_charCount<$charOffset){ + $charOffset=$this->_charCount; + } + $this->_currentPos = $charOffset; + } + + /** + * @see Swift_CharacterStream::write() + * + * @param string $chars + */ + public function write($chars) + { + if (!isset($this->_charReader)) + { + $this->_charReader = $this->_charReaderFactory->getReaderFor( + $this->_charset); + $this->_map = array(); + $this->_mapType = $this->_charReader->getMapType(); + } + $ignored=''; + $this->_datas .= $chars; + $this->_charCount += $this->_charReader->getCharPositions(substr($this->_datas, $this->_datasSize), $this->_datasSize, $this->_map, $ignored); + if ($ignored!==false) { + $this->_datasSize=strlen($this->_datas)-strlen($ignored); + } + else + { + $this->_datasSize=strlen($this->_datas); + } + } +} \ No newline at end of file diff --git a/lib/Swift/lib/classes/Swift/DependencyContainer.php b/lib/Swift/lib/classes/Swift/DependencyContainer.php new file mode 100644 index 0000000..b6ba554 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/DependencyContainer.php @@ -0,0 +1,349 @@ +_store); + } + + /** + * Test if an item is registered in this container with the given name. + * @param string $itemName + * @return boolean + * @see register() + */ + public function has($itemName) + { + return array_key_exists($itemName, $this->_store) + && isset($this->_store[$itemName]['lookupType']); + } + + /** + * Lookup the item with the given $itemName. + * @param string $itemName + * @return mixed + * @throws Swift_DependencyException If the dependency is not found + * @see register() + */ + public function lookup($itemName) + { + if (!$this->has($itemName)) + { + throw new Swift_DependencyException( + 'Cannot lookup dependency "' . $itemName . '" since it is not registered.' + ); + } + + switch ($this->_store[$itemName]['lookupType']) + { + case self::TYPE_ALIAS: + return $this->_createAlias($itemName); + case self::TYPE_VALUE: + return $this->_getValue($itemName); + case self::TYPE_INSTANCE: + return $this->_createNewInstance($itemName); + case self::TYPE_SHARED: + return $this->_createSharedInstance($itemName); + } + } + + /** + * Create an array of arguments passed to the constructor of $itemName. + * @param string $itemName + * @return array + */ + public function createDependenciesFor($itemName) + { + $args = array(); + if (isset($this->_store[$itemName]['args'])) + { + $args = $this->_resolveArgs($this->_store[$itemName]['args']); + } + return $args; + } + + /** + * Register a new dependency with $itemName. + * This method returns the current DependencyContainer instance because it + * requires the use of the fluid interface to set the specific details for the + * dependency. + * + * @param string $itemName + * @return Swift_DependencyContainer + * @see asNewInstanceOf(), asSharedInstanceOf(), asValue() + */ + public function register($itemName) + { + $this->_store[$itemName] = array(); + $this->_endPoint =& $this->_store[$itemName]; + return $this; + } + + /** + * Specify the previously registered item as a literal value. + * {@link register()} must be called before this will work. + * + * @param mixed $value + * @return Swift_DependencyContainer + */ + public function asValue($value) + { + $endPoint =& $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_VALUE; + $endPoint['value'] = $value; + return $this; + } + + /** + * Specify the previously registered item as an alias of another item. + * @param string $lookup + * @return Swift_DependencyContainer + */ + public function asAliasOf($lookup) + { + $endPoint =& $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_ALIAS; + $endPoint['ref'] = $lookup; + return $this; + } + + /** + * Specify the previously registered item as a new instance of $className. + * {@link register()} must be called before this will work. + * Any arguments can be set with {@link withDependencies()}, + * {@link addConstructorValue()} or {@link addConstructorLookup()}. + * + * @param string $className + * @return Swift_DependencyContainer + * @see withDependencies(), addConstructorValue(), addConstructorLookup() + */ + public function asNewInstanceOf($className) + { + $endPoint =& $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_INSTANCE; + $endPoint['className'] = $className; + return $this; + } + + /** + * Specify the previously registered item as a shared instance of $className. + * {@link register()} must be called before this will work. + * @param string $className + * @return Swift_DependencyContainer + */ + public function asSharedInstanceOf($className) + { + $endPoint =& $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_SHARED; + $endPoint['className'] = $className; + return $this; + } + + /** + * Specify a list of injected dependencies for the previously registered item. + * This method takes an array of lookup names. + * + * @param array $lookups + * @return Swift_DependencyContainer + * @see addConstructorValue(), addConstructorLookup() + */ + public function withDependencies(array $lookups) + { + $endPoint =& $this->_getEndPoint(); + $endPoint['args'] = array(); + foreach ($lookups as $lookup) + { + $this->addConstructorLookup($lookup); + } + return $this; + } + + /** + * Specify a literal (non looked up) value for the constructor of the + * previously registered item. + * + * @param mixed $value + * @return Swift_DependencyContainer + * @see withDependencies(), addConstructorLookup() + */ + public function addConstructorValue($value) + { + $endPoint =& $this->_getEndPoint(); + if (!isset($endPoint['args'])) + { + $endPoint['args'] = array(); + } + $endPoint['args'][] = array('type' => 'value', 'item' => $value); + return $this; + } + + /** + * Specify a dependency lookup for the constructor of the previously + * registered item. + * + * @param string $lookup + * @return Swift_DependencyContainer + * @see withDependencies(), addConstructorValue() + */ + public function addConstructorLookup($lookup) + { + $endPoint =& $this->_getEndPoint(); + if (!isset($this->_endPoint['args'])) + { + $endPoint['args'] = array(); + } + $endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup); + return $this; + } + + // -- Private methods + + /** Get the literal value with $itemName */ + private function _getValue($itemName) + { + return $this->_store[$itemName]['value']; + } + + /** Resolve an alias to another item */ + private function _createAlias($itemName) + { + return $this->lookup($this->_store[$itemName]['ref']); + } + + /** Create a fresh instance of $itemName */ + private function _createNewInstance($itemName) + { + $reflector = new ReflectionClass($this->_store[$itemName]['className']); + if ($reflector->getConstructor()) + { + return $reflector->newInstanceArgs( + $this->createDependenciesFor($itemName) + ); + } + else + { + return $reflector->newInstance(); + } + } + + /** Create and register a shared instance of $itemName */ + private function _createSharedInstance($itemName) + { + if (!isset($this->_store[$itemName]['instance'])) + { + $this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName); + } + return $this->_store[$itemName]['instance']; + } + + /** Get the current endpoint in the store */ + private function &_getEndPoint() + { + if (!isset($this->_endPoint)) + { + throw new BadMethodCallException( + 'Component must first be registered by calling register()' + ); + } + return $this->_endPoint; + } + + /** Get an argument list with dependencies resolved */ + private function _resolveArgs(array $args) + { + $resolved = array(); + foreach ($args as $argDefinition) + { + switch ($argDefinition['type']) + { + case 'lookup': + $resolved[] = $this->_lookupRecursive($argDefinition['item']); + break; + case 'value': + $resolved[] = $argDefinition['item']; + break; + } + } + return $resolved; + } + + /** Resolve a single dependency with an collections */ + private function _lookupRecursive($item) + { + if (is_array($item)) + { + $collection = array(); + foreach ($item as $k => $v) + { + $collection[$k] = $this->_lookupRecursive($v); + } + return $collection; + } + else + { + return $this->lookup($item); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/DependencyException.php b/lib/Swift/lib/classes/Swift/DependencyException.php new file mode 100644 index 0000000..bb1681c --- /dev/null +++ b/lib/Swift/lib/classes/Swift/DependencyException.php @@ -0,0 +1,30 @@ +createDependenciesFor('mime.embeddedfile') + ); + + $this->setBody($data); + $this->setFilename($filename); + if ($contentType) + { + $this->setContentType($contentType); + } + } + + /** + * Create a new EmbeddedFile. + * @param string|Swift_OutputByteStream $data + * @param string $filename + * @param string $contentType + * @return Swift_Mime_EmbeddedFile + */ + public static function newInstance($data = null, $filename = null, + $contentType = null) + { + return new self($data, $filename, $contentType); + } + + /** + * Create a new EmbeddedFile from a filesystem path. + * @param string $path + * @return Swift_Mime_EmbeddedFile + */ + public static function fromPath($path) + { + return self::newInstance()->setFile( + new Swift_ByteStream_FileByteStream($path) + ); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Encoder.php b/lib/Swift/lib/classes/Swift/Encoder.php new file mode 100644 index 0000000..32aa96a --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Encoder.php @@ -0,0 +1,32 @@ += $maxLineLength || 76 < $maxLineLength) + { + $maxLineLength = 76; + } + + $encodedString = base64_encode($string); + $firstLine = ''; + + if (0 != $firstLineOffset) + { + $firstLine = substr( + $encodedString, 0, $maxLineLength - $firstLineOffset + ) . "\r\n"; + $encodedString = substr( + $encodedString, $maxLineLength - $firstLineOffset + ); + } + + return $firstLine . trim(chunk_split($encodedString, $maxLineLength, "\r\n")); + } + + /** + * Does nothing. + */ + public function charsetChanged($charset) + { + } + +} diff --git a/lib/Swift/lib/classes/Swift/Encoder/QpEncoder.php b/lib/Swift/lib/classes/Swift/Encoder/QpEncoder.php new file mode 100644 index 0000000..6914f6c --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Encoder/QpEncoder.php @@ -0,0 +1,263 @@ + '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04', + 5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09', + 10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E', + 15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13', + 20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18', + 25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D', + 30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22', + 35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27', + 40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C', + 45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31', + 50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36', + 55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B', + 60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40', + 65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45', + 70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A', + 75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F', + 80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54', + 85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59', + 90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E', + 95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63', + 100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68', + 105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D', + 110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72', + 115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77', + 120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C', + 125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81', + 130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86', + 135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B', + 140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90', + 145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95', + 150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A', + 155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F', + 160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4', + 165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9', + 170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE', + 175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3', + 180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8', + 185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD', + 190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2', + 195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7', + 200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC', + 205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1', + 210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6', + 215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB', + 220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0', + 225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5', + 230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA', + 235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF', + 240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4', + 245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9', + 250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE', + 255 => '=FF' + ); + + /** + * A map of non-encoded ascii characters. + * @var string[] + * @access protected + */ + protected static $_safeMap = array(); + + /** + * Creates a new QpEncoder for the given CharacterStream. + * @param Swift_CharacterStream $charStream to use for reading characters + * @param Swift_StreamFilter $filter if input should be canonicalized + */ + public function __construct(Swift_CharacterStream $charStream, + Swift_StreamFilter $filter = null) + { + $this->_charStream = $charStream; + if (empty(self::$_safeMap)) + { + foreach (array_merge( + array(0x09, 0x20), range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) + { + self::$_safeMap[$byte] = chr($byte); + } + } + $this->_filter = $filter; + } + + /** + * Takes an unencoded string and produces a QP encoded string from it. + * QP encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + * @param string $string to encode + * @param int $firstLineOffset, optional + * @param int $maxLineLength, optional, 0 indicates the default of 76 chars + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, + $maxLineLength = 0) + { + if ($maxLineLength > 76 || $maxLineLength <= 0) + { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $lines = array(); + $lNo = 0; + $lines[$lNo] = ''; + $currentLine =& $lines[$lNo++]; + $size=$lineLen=0; + + $this->_charStream->flushContents(); + $this->_charStream->importString($string); + + //Fetching more than 4 chars at one is slower, as is fetching fewer bytes + // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6 + // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes + while (false !== $bytes = $this->_nextSequence()) + { + //If we're filtering the input + if (isset($this->_filter)) + { + //If we can't filter because we need more bytes + while ($this->_filter->shouldBuffer($bytes)) + { + //Then collect bytes into the buffer + if (false === $moreBytes = $this->_nextSequence(1)) + { + break; + } + + foreach ($moreBytes as $b) + { + $bytes[] = $b; + } + } + //And filter them + $bytes = $this->_filter->filter($bytes); + } + + $enc = $this->_encodeByteSequence($bytes, $size); + if ($currentLine && $lineLen+$size >= $thisLineLength) + { + $lines[$lNo] = ''; + $currentLine =& $lines[$lNo++]; + $thisLineLength = $maxLineLength; + $lineLen=0; + } + $lineLen+=$size; + $currentLine .= $enc; + } + + return $this->_standardize(implode("=\r\n", $lines)); + } + + /** + * Updates the charset used. + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_charStream->setCharacterSet($charset); + } + + // -- Protected methods + + /** + * Encode the given byte array into a verbatim QP form. + * @param int[] $bytes + * @return string + * @access protected + */ + protected function _encodeByteSequence(array $bytes, &$size) + { + $ret = ''; + $size=0; + foreach ($bytes as $b) + { + if (isset(self::$_safeMap[$b])) + { + $ret .= self::$_safeMap[$b]; + ++$size; + } + else + { + $ret .= self::$_qpMap[$b]; + $size+=3; + } + } + return $ret; + } + + /** + * Get the next sequence of bytes to read from the char stream. + * @param int $size number of bytes to read + * @return int[] + * @access protected + */ + protected function _nextSequence($size = 4) + { + return $this->_charStream->readBytes($size); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + * @param string $string + * @return string + * @access protected + */ + protected function _standardize($string) + { + $string = str_replace(array("\t=0D=0A", " =0D=0A", "=0D=0A"), + array("=09\r\n", "=20\r\n", "\r\n"), $string + ); + switch ($end = ord(substr($string, -1))) + { + case 0x09: + case 0x20: + $string = substr_replace($string, self::$_qpMap[$end], -1); + } + return $string; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Encoder/Rfc2231Encoder.php b/lib/Swift/lib/classes/Swift/Encoder/Rfc2231Encoder.php new file mode 100644 index 0000000..febc6ba --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Encoder/Rfc2231Encoder.php @@ -0,0 +1,89 @@ +_charStream = $charStream; + } + + /** + * Takes an unencoded string and produces a string encoded according to + * RFC 2231 from it. + * @param string $string to encode + * @param int $firstLineOffset + * @param int $maxLineLength, optional, 0 indicates the default of 75 bytes + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, + $maxLineLength = 0) + { + $lines = array(); $lineCount = 0; + $lines[] = ''; + $currentLine =& $lines[$lineCount++]; + + if (0 >= $maxLineLength) + { + $maxLineLength = 75; + } + + $this->_charStream->flushContents(); + $this->_charStream->importString($string); + + $thisLineLength = $maxLineLength - $firstLineOffset; + + while (false !== $char = $this->_charStream->read(4)) + { + $encodedChar = rawurlencode($char); + if (0 != strlen($currentLine) + && strlen($currentLine . $encodedChar) > $thisLineLength) + { + $lines[] = ''; + $currentLine =& $lines[$lineCount++]; + $thisLineLength = $maxLineLength; + } + $currentLine .= $encodedChar; + } + + return implode("\r\n", $lines); + } + + /** + * Updates the charset used. + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_charStream->setCharacterSet($charset); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Encoding.php b/lib/Swift/lib/classes/Swift/Encoding.php new file mode 100644 index 0000000..1849a82 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Encoding.php @@ -0,0 +1,70 @@ +lookup($key); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Events/CommandEvent.php b/lib/Swift/lib/classes/Swift/Events/CommandEvent.php new file mode 100644 index 0000000..73eb585 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/CommandEvent.php @@ -0,0 +1,67 @@ +_command = $command; + $this->_successCodes = $successCodes; + } + + /** + * Get the command which was sent to the server. + * @return string + */ + public function getCommand() + { + return $this->_command; + } + + /** + * Get the numeric response codes which indicate success for this command. + * @return int[] + */ + public function getSuccessCodes() + { + return $this->_successCodes; + } + +} \ No newline at end of file diff --git a/lib/Swift/lib/classes/Swift/Events/CommandListener.php b/lib/Swift/lib/classes/Swift/Events/CommandListener.php new file mode 100644 index 0000000..2fd7117 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/CommandListener.php @@ -0,0 +1,29 @@ +_source = $source; + } + + /** + * Get the source object of this event. + * @return object + */ + public function getSource() + { + return $this->_source; + } + + /** + * Prevent this Event from bubbling any further up the stack. + * @param boolean $cancel, optional + */ + public function cancelBubble($cancel = true) + { + $this->_bubbleCancelled = $cancel; + } + + /** + * Returns true if this Event will not bubble any further up the stack. + * @return boolean + */ + public function bubbleCancelled() + { + return $this->_bubbleCancelled; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Events/ResponseEvent.php b/lib/Swift/lib/classes/Swift/Events/ResponseEvent.php new file mode 100644 index 0000000..addf9e7 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/ResponseEvent.php @@ -0,0 +1,65 @@ +_response = $response; + $this->_valid = $valid; + } + + /** + * Get the response which was received from the server. + * @return string + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Get the success status of this Event. + * @return boolean + */ + public function isValid() + { + return $this->_valid; + } + +} \ No newline at end of file diff --git a/lib/Swift/lib/classes/Swift/Events/ResponseListener.php b/lib/Swift/lib/classes/Swift/Events/ResponseListener.php new file mode 100644 index 0000000..092385b --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/ResponseListener.php @@ -0,0 +1,29 @@ +_message = $message; + $this->_result = self::RESULT_PENDING; + } + + /** + * Get the Transport used to send the Message. + * @return Swift_Transport + */ + public function getTransport() + { + return $this->getSource(); + } + + /** + * Get the Message being sent. + * @return Swift_Mime_Message + */ + public function getMessage() + { + return $this->_message; + } + + /** + * Set the array of addresses that failed in sending. + * @param array $recipients + */ + public function setFailedRecipients($recipients) + { + $this->_failedRecipients = $recipients; + } + + /** + * Get an recipient addresses which were not accepted for delivery. + * @return string[] + */ + public function getFailedRecipients() + { + return $this->_failedRecipients; + } + + /** + * Set the result of sending. + * @return int + */ + public function setResult($result) + { + $this->_result = $result; + } + + /** + * Get the result of this Event. + * The return value is a bitmask from + * {@link RESULT_PENDING, RESULT_SUCCESS, RESULT_TENTATIVE, RESULT_FAILED} + * @return int + */ + public function getResult() + { + return $this->_result; + } + +} \ No newline at end of file diff --git a/lib/Swift/lib/classes/Swift/Events/SendListener.php b/lib/Swift/lib/classes/Swift/Events/SendListener.php new file mode 100644 index 0000000..a8f0cc3 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/SendListener.php @@ -0,0 +1,35 @@ +_eventMap = array( + 'Swift_Events_CommandEvent' => 'Swift_Events_CommandListener', + 'Swift_Events_ResponseEvent' => 'Swift_Events_ResponseListener', + 'Swift_Events_SendEvent' => 'Swift_Events_SendListener', + 'Swift_Events_TransportChangeEvent' => 'Swift_Events_TransportChangeListener', + 'Swift_Events_TransportExceptionEvent' => 'Swift_Events_TransportExceptionListener' + ); + } + + /** + * Create a new SendEvent for $source and $message. + * + * @param Swift_Transport $source + * @param Swift_Mime_Message + * @return Swift_Events_SendEvent + */ + public function createSendEvent(Swift_Transport $source, + Swift_Mime_Message $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + /** + * Create a new CommandEvent for $source and $command. + * + * @param Swift_Transport $source + * @param string $command That will be executed + * @param array $successCodes That are needed + * @return Swift_Events_CommandEvent + */ + public function createCommandEvent(Swift_Transport $source, + $command, $successCodes = array()) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + /** + * Create a new ResponseEvent for $source and $response. + * + * @param Swift_Transport $source + * @param string $response + * @param boolean $valid If the response is valid + * @return Swift_Events_ResponseEvent + */ + public function createResponseEvent(Swift_Transport $source, + $response, $valid) + { + return new Swift_Events_ResponseEvent($source, $response, $valid); + } + + /** + * Create a new TransportChangeEvent for $source. + * + * @param Swift_Transport $source + * @return Swift_Events_TransportChangeEvent + */ + public function createTransportChangeEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + /** + * Create a new TransportExceptionEvent for $source. + * + * @param Swift_Transport $source + * @param Swift_TransportException $ex + * @return Swift_Events_TransportExceptionEvent + */ + public function createTransportExceptionEvent(Swift_Transport $source, + Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($source, $ex); + } + + /** + * Bind an event listener to this dispatcher. + * + * @param Swift_Events_EventListener $listener + */ + public function bindEventListener(Swift_Events_EventListener $listener) + { + foreach ($this->_listeners as $l) + { + //Already loaded + if ($l === $listener) + { + return; + } + } + $this->_listeners[] = $listener; + } + + /** + * Dispatch the given Event to all suitable listeners. + * + * @param Swift_Events_EventObject $evt + * @param string $target method + */ + public function dispatchEvent(Swift_Events_EventObject $evt, $target) + { + $this->_prepareBubbleQueue($evt); + $this->_bubble($evt, $target); + } + + // -- Private methods + + /** Queue listeners on a stack ready for $evt to be bubbled up it */ + private function _prepareBubbleQueue(Swift_Events_EventObject $evt) + { + $this->_bubbleQueue = array(); + $evtClass = get_class($evt); + foreach ($this->_listeners as $listener) + { + if (array_key_exists($evtClass, $this->_eventMap) + && ($listener instanceof $this->_eventMap[$evtClass])) + { + $this->_bubbleQueue[] = $listener; + } + } + } + + /** Bubble $evt up the stack calling $target() on each listener */ + private function _bubble(Swift_Events_EventObject $evt, $target) + { + if (!$evt->bubbleCancelled() && $listener = array_shift($this->_bubbleQueue)) + { + $listener->$target($evt); + $this->_bubble($evt, $target); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Events/TransportChangeEvent.php b/lib/Swift/lib/classes/Swift/Events/TransportChangeEvent.php new file mode 100644 index 0000000..f069a4c --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/TransportChangeEvent.php @@ -0,0 +1,31 @@ +getSource(); + } + +} \ No newline at end of file diff --git a/lib/Swift/lib/classes/Swift/Events/TransportChangeListener.php b/lib/Swift/lib/classes/Swift/Events/TransportChangeListener.php new file mode 100644 index 0000000..ba729d0 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/TransportChangeListener.php @@ -0,0 +1,53 @@ +_exception = $ex; + } + + /** + * Get the TransportException thrown. + * @return Swift_TransportException + */ + public function getException() + { + return $this->_exception; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Events/TransportExceptionListener.php b/lib/Swift/lib/classes/Swift/Events/TransportExceptionListener.php new file mode 100644 index 0000000..d6dce94 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Events/TransportExceptionListener.php @@ -0,0 +1,30 @@ +createDependenciesFor('transport.failover') + ); + + $this->setTransports($transports); + } + + /** + * Create a new FailoverTransport instance. + * @param string $transports + * @return Swift_FailoverTransport + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } + +} diff --git a/lib/Swift/lib/classes/Swift/FileStream.php b/lib/Swift/lib/classes/Swift/FileStream.php new file mode 100644 index 0000000..a7f894d --- /dev/null +++ b/lib/Swift/lib/classes/Swift/FileStream.php @@ -0,0 +1,28 @@ +setFile( + new Swift_ByteStream_FileByteStream($path) + ); + return $image; + } + +} diff --git a/lib/Swift/lib/classes/Swift/InputByteStream.php b/lib/Swift/lib/classes/Swift/InputByteStream.php new file mode 100644 index 0000000..e8f45f4 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/InputByteStream.php @@ -0,0 +1,72 @@ +_stream = $stream; + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param int $mode + * @see MODE_WRITE, MODE_APPEND + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) + { + case self::MODE_WRITE: + $this->_contents[$nsKey][$itemKey] = $string; + break; + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) + { + $this->_contents[$nsKey][$itemKey] = ''; + } + $this->_contents[$nsKey][$itemKey] .= $string; + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + } + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * @param string $nsKey + * @param string $itemKey + * @param Swift_OutputByteStream $os + * @param int $mode + * @see MODE_WRITE, MODE_APPEND + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, + $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) + { + case self::MODE_WRITE: + $this->clearKey($nsKey, $itemKey); + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) + { + $this->_contents[$nsKey][$itemKey] = ''; + } + while (false !== $bytes = $os->read(8192)) + { + $this->_contents[$nsKey][$itemKey] .= $bytes; + } + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + } + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * NOTE: The stream will always write in append mode. + * @param string $nsKey + * @param string $itemKey + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, + Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->_stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) + { + $is->setWriteThroughStream($writeThrough); + } + return $is; + } + + /** + * Get data back out of the cache as a string. + * @param string $nsKey + * @param string $itemKey + * @return string + */ + public function getString($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) + { + return $this->_contents[$nsKey][$itemKey]; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + $this->_prepareCache($nsKey); + $is->write($this->getString($nsKey, $itemKey)); + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * @param string $nsKey + * @param string $itemKey + * @return boolean + */ + public function hasKey($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + return array_key_exists($itemKey, $this->_contents[$nsKey]); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + unset($this->_contents[$nsKey][$itemKey]); + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * @param string $nsKey + */ + public function clearAll($nsKey) + { + unset($this->_contents[$nsKey]); + } + + // -- Private methods + + /** + * Initialize the namespace of $nsKey if needed. + * @param string $nsKey + * @access private + */ + private function _prepareCache($nsKey) + { + if (!array_key_exists($nsKey, $this->_contents)) + { + $this->_contents[$nsKey] = array(); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/KeyCache/DiskKeyCache.php b/lib/Swift/lib/classes/Swift/KeyCache/DiskKeyCache.php new file mode 100644 index 0000000..599fd6c --- /dev/null +++ b/lib/Swift/lib/classes/Swift/KeyCache/DiskKeyCache.php @@ -0,0 +1,316 @@ +_stream = $stream; + $this->_path = $path; + $this->_quotes = get_magic_quotes_runtime(); + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param int $mode + * @throws Swift_IoException + * @see MODE_WRITE, MODE_APPEND + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) + { + case self::MODE_WRITE: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + break; + } + fwrite($fp, $string); + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * @param string $nsKey + * @param string $itemKey + * @param Swift_OutputByteStream $os + * @param int $mode + * @see MODE_WRITE, MODE_APPEND + * @throws Swift_IoException + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, + $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) + { + case self::MODE_WRITE: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + break; + } + while (false !== $bytes = $os->read(8192)) + { + fwrite($fp, $bytes); + } + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * NOTE: The stream will always write in append mode. + * @param string $nsKey + * @param string $itemKey + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, + Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->_stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) + { + $is->setWriteThroughStream($writeThrough); + } + return $is; + } + + /** + * Get data back out of the cache as a string. + * @param string $nsKey + * @param string $itemKey + * @return string + * @throws Swift_IoException + */ + public function getString($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) + { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + if ($this->_quotes) + { + set_magic_quotes_runtime(0); + } + $str = ''; + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) + { + $str .= $bytes; + } + if ($this->_quotes) + { + set_magic_quotes_runtime(1); + } + return $str; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + if ($this->hasKey($nsKey, $itemKey)) + { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + if ($this->_quotes) + { + set_magic_quotes_runtime(0); + } + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) + { + $is->write($bytes); + } + if ($this->_quotes) + { + set_magic_quotes_runtime(1); + } + } + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * @param string $nsKey + * @param string $itemKey + * @return boolean + */ + public function hasKey($nsKey, $itemKey) + { + return is_file($this->_path . '/' . $nsKey . '/' . $itemKey); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + if ($this->hasKey($nsKey, $itemKey)) + { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END); + fclose($fp); + unlink($this->_path . '/' . $nsKey . '/' . $itemKey); + } + unset($this->_keys[$nsKey][$itemKey]); + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * @param string $nsKey + */ + public function clearAll($nsKey) + { + if (array_key_exists($nsKey, $this->_keys)) + { + foreach ($this->_keys[$nsKey] as $itemKey=>$null) + { + $this->clearKey($nsKey, $itemKey); + } + rmdir($this->_path . '/' . $nsKey); + unset($this->_keys[$nsKey]); + } + } + + // -- Private methods + + /** + * Initialize the namespace of $nsKey if needed. + * @param string $nsKey + * @access private + */ + private function _prepareCache($nsKey) + { + $cacheDir = $this->_path . '/' . $nsKey; + if (!is_dir($cacheDir)) + { + if (!mkdir($cacheDir)) + { + throw new Swift_IoException('Failed to create cache directory ' . $cacheDir); + } + $this->_keys[$nsKey] = array(); + } + } + + /** + * Get a file handle on the cache item. + * @param string $nsKey + * @param string $itemKey + * @param int $position + * @return resource + * @access private + */ + private function _getHandle($nsKey, $itemKey, $position) + { + if (!isset($this->_keys[$nsKey]) || !array_key_exists($itemKey, $this->_keys[$nsKey])) + { + $fp = fopen($this->_path . '/' . $nsKey . '/' . $itemKey, 'w+b'); + $this->_keys[$nsKey][$itemKey] = $fp; + } + if (self::POSITION_START == $position) + { + fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_SET); + } + else + { + fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_END); + } + return $this->_keys[$nsKey][$itemKey]; + } + + /** + * Destructor. + */ + public function __destruct() + { + foreach ($this->_keys as $nsKey=>$null) + { + $this->clearAll($nsKey); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/KeyCache/KeyCacheInputStream.php b/lib/Swift/lib/classes/Swift/KeyCache/KeyCacheInputStream.php new file mode 100644 index 0000000..a1f4440 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/KeyCache/KeyCacheInputStream.php @@ -0,0 +1,53 @@ +_keyCache = $keyCache; + } + + /** + * Specify a stream to write through for each write(). + * @param Swift_InputByteStream $is + */ + public function setWriteThroughStream(Swift_InputByteStream $is) + { + $this->_writeThrough = $is; + } + + /** + * Writes $bytes to the end of the stream. + * @param string $bytes + * @param Swift_InputByteStream $is, optional + */ + public function write($bytes, Swift_InputByteStream $is = null) + { + $this->_keyCache->setString( + $this->_nsKey, $this->_itemKey, $bytes, Swift_KeyCache::MODE_APPEND + ); + if (isset($is)) + { + $is->write($bytes); + } + if (isset($this->_writeThrough)) + { + $this->_writeThrough->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Not used. + */ + public function bind(Swift_InputByteStream $is) + { + } + + /** + * Not used. + */ + public function unbind(Swift_InputByteStream $is) + { + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + */ + public function flushBuffers() + { + $this->_keyCache->clearKey($this->_nsKey, $this->_itemKey); + } + + /** + * Set the nsKey which will be written to. + * @param string $nsKey + */ + public function setNsKey($nsKey) + { + $this->_nsKey = $nsKey; + } + + /** + * Set the itemKey which will be written to. + * @param string $itemKey + */ + public function setItemKey($itemKey) + { + $this->_itemKey = $itemKey; + } + + /** + * Any implementation should be cloneable, allowing the clone to access a + * separate $nsKey and $itemKey. + */ + public function __clone() + { + $this->_writeThrough = null; + } + +} diff --git a/lib/Swift/lib/classes/Swift/LoadBalancedTransport.php b/lib/Swift/lib/classes/Swift/LoadBalancedTransport.php new file mode 100644 index 0000000..14ae292 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/LoadBalancedTransport.php @@ -0,0 +1,48 @@ +createDependenciesFor('transport.loadbalanced') + ); + + $this->setTransports($transports); + } + + /** + * Create a new LoadBalancedTransport instance. + * @param string $transports + * @return Swift_LoadBalancedTransport + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } + +} diff --git a/lib/Swift/lib/classes/Swift/MailTransport.php b/lib/Swift/lib/classes/Swift/MailTransport.php new file mode 100644 index 0000000..afe29c6 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/MailTransport.php @@ -0,0 +1,48 @@ +createDependenciesFor('transport.mail') + ); + + $this->setExtraParams($extraParams); + } + + /** + * Create a new MailTransport instance. + * @param string $extraParams To be passed to mail() + * @return Swift_MailTransport + */ + public static function newInstance($extraParams = '-f%s') + { + return new self($extraParams); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mailer.php b/lib/Swift/lib/classes/Swift/Mailer.php new file mode 100644 index 0000000..c92feb4 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mailer.php @@ -0,0 +1,173 @@ +_transport = $transport; + } + + /** + * Create a new Mailer instance. + * + * @param Swift_Transport $transport + * @return Swift_Mailer + */ + public static function newInstance(Swift_Transport $transport) + { + return new self($transport); + } + + /** + * Send the given Message like it would be sent in a mail client. + * + * All recipients (with the exception of Bcc) will be able to see the other + * recipients this message was sent to. + * + * If you need to send to each recipient without disclosing details about the + * other recipients see {@link batchSend()}. + * + * Recipient/sender data will be retreived from the Message object. + * + * The return value is the number of recipients who were accepted for + * delivery. + * + * @param Swift_Mime_Message $message + * @param array &$failedRecipients, optional + * @return int + * @see batchSend() + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + + if (!$this->_transport->isStarted()) + { + $this->_transport->start(); + } + + return $this->_transport->send($message, $failedRecipients); + } + + /** + * Send the given Message to all recipients individually. + * + * This differs from {@link send()} in the way headers are presented to the + * recipient. The only recipient in the "To:" field will be the individual + * recipient it was sent to. + * + * If an iterator is provided, recipients will be read from the iterator + * one-by-one, otherwise recipient data will be retreived from the Message + * object. + * + * Sender information is always read from the Message object. + * + * The return value is the number of recipients who were accepted for + * delivery. + * + * @param Swift_Mime_Message $message + * @param array &$failedRecipients, optional + * @param Swift_Mailer_RecipientIterator $it, optional + * @return int + * @see send() + */ + public function batchSend(Swift_Mime_Message $message, + &$failedRecipients = null, + Swift_Mailer_RecipientIterator $it = null) + { + $failedRecipients = (array) $failedRecipients; + + $sent = 0; + $to = $message->getTo(); + $cc = $message->getCc(); + $bcc = $message->getBcc(); + + if (!empty($cc)) + { + $message->setCc(array()); + } + if (!empty($bcc)) + { + $message->setBcc(array()); + } + + //Use an iterator if set + if (isset($it)) + { + while ($it->hasNext()) + { + $message->setTo($it->nextRecipient()); + $sent += $this->send($message, $failedRecipients); + } + } + else + { + foreach ($to as $address => $name) + { + $message->setTo(array($address => $name)); + $sent += $this->send($message, $failedRecipients); + } + } + + $message->setTo($to); + + if (!empty($cc)) + { + $message->setCc($cc); + } + if (!empty($bcc)) + { + $message->setBcc($bcc); + } + + return $sent; + } + + /** + * Register a plugin using a known unique key (e.g. myPlugin). + * + * @param Swift_Events_EventListener $plugin + * @param string $key + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_transport->registerPlugin($plugin); + } + + /** + * The Transport used to send messages. + * @return Swift_Transport + */ + public function getTransport() + { + return $this->_transport; + } +} diff --git a/lib/Swift/lib/classes/Swift/Mailer/ArrayRecipientIterator.php b/lib/Swift/lib/classes/Swift/Mailer/ArrayRecipientIterator.php new file mode 100644 index 0000000..65d60c1 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mailer/ArrayRecipientIterator.php @@ -0,0 +1,59 @@ +_recipients = $recipients; + } + + /** + * Returns true only if there are more recipients to send to. + * @return boolean + */ + public function hasNext() + { + return !empty($this->_recipients); + } + + /** + * Returns an array where the keys are the addresses of recipients and the + * values are the names. + * e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL) + * @return array + */ + public function nextRecipient() + { + return array_splice($this->_recipients, 0, 1); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mailer/RecipientIterator.php b/lib/Swift/lib/classes/Swift/Mailer/RecipientIterator.php new file mode 100644 index 0000000..2713841 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mailer/RecipientIterator.php @@ -0,0 +1,34 @@ + 'Foo') or ('foo@bar' => NULL) + * @return array + */ + public function nextRecipient(); + +} diff --git a/lib/Swift/lib/classes/Swift/Message.php b/lib/Swift/lib/classes/Swift/Message.php new file mode 100644 index 0000000..e8183ea --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Message.php @@ -0,0 +1,82 @@ +createDependenciesFor('mime.message') + ); + + if (!isset($charset)) + { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setSubject($subject); + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) + { + $this->setContentType($contentType); + } + } + + /** + * Create a new Message. + * @param string $subject + * @param string $body + * @param string $contentType + * @param string $charset + * @return Swift_Mime_Message + */ + public static function newInstance($subject = null, $body = null, + $contentType = null, $charset = null) + { + return new self($subject, $body, $contentType, $charset); + } + + /** + * Add a MimePart to this Message. + * @param string|Swift_OutputByteStream $body + * @param string $contentType + * @param string $charset + */ + public function addPart($body, $contentType = null, $charset = null) + { + return $this->attach(Swift_MimePart::newInstance( + $body, $contentType, $charset + )); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Attachment.php b/lib/Swift/lib/classes/Swift/Mime/Attachment.php new file mode 100644 index 0000000..25ef68b --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Attachment.php @@ -0,0 +1,143 @@ +setDisposition('attachment'); + $this->setContentType('application/octet-stream'); + $this->_mimeTypes = $mimeTypes; + } + + /** + * Get the nesting level used for this attachment. + * Always returns {@link LEVEL_MIXED}. + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_MIXED; + } + + /** + * Get the Content-Disposition of this attachment. + * By default attachments have a disposition of "attachment". + * @return string + */ + public function getDisposition() + { + return $this->_getHeaderFieldModel('Content-Disposition'); + } + + /** + * Set the Content-Disposition of this attachment. + * @param string $disposition + */ + public function setDisposition($disposition) + { + if (!$this->_setHeaderFieldModel('Content-Disposition', $disposition)) + { + $this->getHeaders()->addParameterizedHeader( + 'Content-Disposition', $disposition + ); + } + return $this; + } + + /** + * Get the filename of this attachment when downloaded. + * @return string + */ + public function getFilename() + { + return $this->_getHeaderParameter('Content-Disposition', 'filename'); + } + + /** + * Set the filename of this attachment. + * @param string $filename + */ + public function setFilename($filename) + { + $this->_setHeaderParameter('Content-Disposition', 'filename', $filename); + $this->_setHeaderParameter('Content-Type', 'name', $filename); + return $this; + } + + /** + * Get the file size of this attachment. + * @return int + */ + public function getSize() + { + return $this->_getHeaderParameter('Content-Disposition', 'size'); + } + + /** + * Set the file size of this attachment. + * @param int $size + */ + public function setSize($size) + { + $this->_setHeaderParameter('Content-Disposition', 'size', $size); + return $this; + } + + /** + * Set the file that this attachment is for. + * @param Swift_FileStream $file + * @param string $contentType optional + */ + public function setFile(Swift_FileStream $file, $contentType = null) + { + $this->setFilename(basename($file->getPath())); + $this->setBody($file, $contentType); + if (!isset($contentType)) + { + $extension = strtolower(substr( + $file->getPath(), strrpos($file->getPath(), '.') + 1 + )); + + if (array_key_exists($extension, $this->_mimeTypes)) + { + $this->setContentType($this->_mimeTypes[$extension]); + } + } + return $this; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/CharsetObserver.php b/lib/Swift/lib/classes/Swift/Mime/CharsetObserver.php new file mode 100644 index 0000000..c26009f --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/CharsetObserver.php @@ -0,0 +1,26 @@ += $maxLineLength || 76 < $maxLineLength) + { + $maxLineLength = 76; + } + + $remainder = 0; + + while (false !== $bytes = $os->read(8190)) + { + $encoded = base64_encode($bytes); + $encodedTransformed = ''; + $thisMaxLineLength = $maxLineLength - $remainder - $firstLineOffset; + + while ($thisMaxLineLength < strlen($encoded)) + { + $encodedTransformed .= substr($encoded, 0, $thisMaxLineLength) . "\r\n"; + $firstLineOffset = 0; + $encoded = substr($encoded, $thisMaxLineLength); + $thisMaxLineLength = $maxLineLength; + $remainder = 0; + } + + if (0 < $remainingLength = strlen($encoded)) + { + $remainder += $remainingLength; + $encodedTransformed .= $encoded; + $encoded = null; + } + + $is->write($encodedTransformed); + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'base64'. + * @return string + */ + public function getName() + { + return 'base64'; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php b/lib/Swift/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php new file mode 100644 index 0000000..4a725d8 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php @@ -0,0 +1,175 @@ +_name = $name; + $this->_canonical = $canonical; + } + + /** + * Encode a given string to produce an encoded string. + * @param string $string + * @param int $firstLineOffset, ignored + * @param int $maxLineLength - 0 means no wrapping will occur + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, + $maxLineLength = 0) + { + if ($this->_canonical) + { + $string = $this->_canonicalize($string); + } + return $this->_safeWordWrap($string, $maxLineLength, "\r\n"); + } + + /** + * Encode stream $in to stream $out. + * @param Swift_OutputByteStream $in + * @param Swift_InputByteStream $out + * @param int $firstLineOffset, ignored + * @param int $maxLineLength, optional, 0 means no wrapping will occur + */ + public function encodeByteStream( + Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, + $maxLineLength = 0) + { + $leftOver = ''; + while (false !== $bytes = $os->read(8192)) + { + $toencode = $leftOver . $bytes; + if ($this->_canonical) + { + $toencode = $this->_canonicalize($toencode); + } + $wrapped = $this->_safeWordWrap($toencode, $maxLineLength, "\r\n"); + $lastLinePos = strrpos($wrapped, "\r\n"); + $leftOver = substr($wrapped, $lastLinePos); + $wrapped = substr($wrapped, 0, $lastLinePos); + + $is->write($wrapped); + } + if (strlen($leftOver)) + { + $is->write($leftOver); + } + } + + /** + * Get the name of this encoding scheme. + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } + + // -- Private methods + + /** + * A safer (but weaker) wordwrap for unicode. + * @param string $string + * @param int $length + * @param string $le + * @return string + * @access private + */ + private function _safeWordwrap($string, $length = 75, $le = "\r\n") + { + if (0 >= $length) + { + return $string; + } + + $originalLines = explode($le, $string); + + $lines = array(); + $lineCount = 0; + + foreach ($originalLines as $originalLine) + { + $lines[] = ''; + $currentLine =& $lines[$lineCount++]; + + //$chunks = preg_split('/(?<=[\ \t,\.!\?\-&\+\/])/', $originalLine); + $chunks = preg_split('/(?<=\s)/', $originalLine); + + foreach ($chunks as $chunk) + { + if (0 != strlen($currentLine) + && strlen($currentLine . $chunk) > $length) + { + $lines[] = ''; + $currentLine =& $lines[$lineCount++]; + } + $currentLine .= $chunk; + } + } + + return implode("\r\n", $lines); + } + + /** + * Canonicalize string input (fix CRLF). + * @param string $string + * @return string + * @access private + */ + private function _canonicalize($string) + { + return str_replace( + array("\r\n", "\r", "\n"), + array("\n", "\n", "\r\n"), + $string + ); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php b/lib/Swift/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php new file mode 100644 index 0000000..3beeb63 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php @@ -0,0 +1,117 @@ + 76 || $maxLineLength <= 0) + { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $this->_charStream->flushContents(); + $this->_charStream->importByteStream($os); + + $currentLine = ''; + $prepend = ''; + $size=$lineLen=0; + + while (false !== $bytes = $this->_nextSequence()) + { + //If we're filtering the input + if (isset($this->_filter)) + { + //If we can't filter because we need more bytes + while ($this->_filter->shouldBuffer($bytes)) + { + //Then collect bytes into the buffer + if (false === $moreBytes = $this->_nextSequence(1)) + { + break; + } + + foreach ($moreBytes as $b) + { + $bytes[] = $b; + } + } + //And filter them + $bytes = $this->_filter->filter($bytes); + } + + $enc = $this->_encodeByteSequence($bytes, $size); + if ($currentLine && $lineLen+$size >= $thisLineLength) + { + $is->write($prepend . $this->_standardize($currentLine)); + $currentLine = ''; + $prepend = "=\r\n"; + $thisLineLength = $maxLineLength; + $lineLen=0; + } + $lineLen+=$size; + $currentLine .= $enc; + } + if (strlen($currentLine)) + { + $is->write($prepend . $this->_standardize($currentLine)); + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'quoted-printable'. + * @return string + */ + public function getName() + { + return 'quoted-printable'; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/EmbeddedFile.php b/lib/Swift/lib/classes/Swift/Mime/EmbeddedFile.php new file mode 100644 index 0000000..983b78d --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/EmbeddedFile.php @@ -0,0 +1,51 @@ +setDisposition('inline'); + $this->setId($this->getId()); + } + + /** + * Get the nesting level of this EmbeddedFile. + * Returns {@link LEVEL_RELATED}. + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_RELATED; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/EncodingObserver.php b/lib/Swift/lib/classes/Swift/Mime/EncodingObserver.php new file mode 100644 index 0000000..50472db --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/EncodingObserver.php @@ -0,0 +1,28 @@ +clearCachedValueIf($charset != $this->_charset); + $this->_charset = $charset; + if (isset($this->_encoder)) + { + $this->_encoder->charsetChanged($charset); + } + } + + /** + * Get the character set used in this Header. + * @return string + */ + public function getCharset() + { + return $this->_charset; + } + + /** + * Set the language used in this Header. + * For example, for US English, 'en-us'. + * This can be unspecified. + * @param string $lang + */ + public function setLanguage($lang) + { + $this->clearCachedValueIf($this->_lang != $lang); + $this->_lang = $lang; + } + + /** + * Get the language used in this Header. + * @return string + */ + public function getLanguage() + { + return $this->_lang; + } + + /** + * Set the encoder used for encoding the header. + * @param Swift_Mime_HeaderEncoder $encoder + */ + public function setEncoder(Swift_Mime_HeaderEncoder $encoder) + { + $this->_encoder = $encoder; + $this->setCachedValue(null); + } + + /** + * Get the encoder used for encoding this Header. + * @return Swift_Mime_HeaderEncoder + */ + public function getEncoder() + { + return $this->_encoder; + } + + /** + * Get the name of this header (e.g. charset). + * @return string + */ + public function getFieldName() + { + return $this->_name; + } + + /** + * Set the maximum length of lines in the header (excluding EOL). + * @param int $lineLength + */ + public function setMaxLineLength($lineLength) + { + $this->clearCachedValueIf($this->_lineLength != $lineLength); + $this->_lineLength = $lineLength; + } + + /** + * Get the maximum permitted length of lines in this Header. + * @return int + */ + public function getMaxLineLength() + { + return $this->_lineLength; + } + + /** + * Get this Header rendered as a RFC 2822 compliant string. + * @return string + * @throws Swift_RfcComplianceException + */ + public function toString() + { + return $this->_tokensToString($this->toTokens()); + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + // -- Points of extension + + /** + * Set the name of this Header field. + * @param string $name + * @access protected + */ + protected function setFieldName($name) + { + $this->_name = $name; + } + + /** + * Initialize some RFC 2822 (and friends) ABNF grammar definitions. + * @access protected + */ + protected function initializeGrammar() + { + $this->_specials = array( + '(', ')', '<', '>', '[', ']', + ':', ';', '@', ',', '.', '"' + ); + + /*** Refer to RFC 2822 for ABNF grammar ***/ + + //All basic building blocks + $this->_grammar['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]'; + $this->_grammar['WSP'] = '[ \t]'; + $this->_grammar['CRLF'] = '(?:\r\n)'; + $this->_grammar['FWS'] = '(?:(?:' . $this->_grammar['WSP'] . '*' . + $this->_grammar['CRLF'] . ')?' . $this->_grammar['WSP'] . ')'; + $this->_grammar['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]'; + $this->_grammar['quoted-pair'] = '(?:\\\\' . $this->_grammar['text'] . ')'; + $this->_grammar['ctext'] = '(?:' . $this->_grammar['NO-WS-CTL'] . + '|[\x21-\x27\x2A-\x5B\x5D-\x7E])'; + //Uses recursive PCRE (?1) -- could be a weak point?? + $this->_grammar['ccontent'] = '(?:' . $this->_grammar['ctext'] . '|' . + $this->_grammar['quoted-pair'] . '|(?1))'; + $this->_grammar['comment'] = '(\((?:' . $this->_grammar['FWS'] . '|' . + $this->_grammar['ccontent']. ')*' . $this->_grammar['FWS'] . '?\))'; + $this->_grammar['CFWS'] = '(?:(?:' . $this->_grammar['FWS'] . '?' . + $this->_grammar['comment'] . ')*(?:(?:' . $this->_grammar['FWS'] . '?' . + $this->_grammar['comment'] . ')|' . $this->_grammar['FWS'] . '))'; + $this->_grammar['qtext'] = '(?:' . $this->_grammar['NO-WS-CTL'] . + '|[\x21\x23-\x5B\x5D-\x7E])'; + $this->_grammar['qcontent'] = '(?:' . $this->_grammar['qtext'] . '|' . + $this->_grammar['quoted-pair'] . ')'; + $this->_grammar['quoted-string'] = '(?:' . $this->_grammar['CFWS'] . '?"' . + '(' . $this->_grammar['FWS'] . '?' . $this->_grammar['qcontent'] . ')*' . + $this->_grammar['FWS'] . '?"' . $this->_grammar['CFWS'] . '?)'; + $this->_grammar['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]'; + $this->_grammar['atom'] = '(?:' . $this->_grammar['CFWS'] . '?' . + $this->_grammar['atext'] . '+' . $this->_grammar['CFWS'] . '?)'; + $this->_grammar['dot-atom-text'] = '(?:' . $this->_grammar['atext'] . '+' . + '(\.' . $this->_grammar['atext'] . '+)*)'; + $this->_grammar['dot-atom'] = '(?:' . $this->_grammar['CFWS'] . '?' . + $this->_grammar['dot-atom-text'] . '+' . $this->_grammar['CFWS'] . '?)'; + $this->_grammar['word'] = '(?:' . $this->_grammar['atom'] . '|' . + $this->_grammar['quoted-string'] . ')'; + $this->_grammar['phrase'] = '(?:' . $this->_grammar['word'] . '+?)'; + $this->_grammar['no-fold-quote'] = '(?:"(?:' . $this->_grammar['qtext'] . + '|' . $this->_grammar['quoted-pair'] . ')*")'; + $this->_grammar['dtext'] = '(?:' . $this->_grammar['NO-WS-CTL'] . + '|[\x21-\x5A\x5E-\x7E])'; + $this->_grammar['no-fold-literal'] = '(?:\[(?:' . $this->_grammar['dtext'] . + '|' . $this->_grammar['quoted-pair'] . ')*\])'; + + //Message IDs + $this->_grammar['id-left'] = '(?:' . $this->_grammar['dot-atom-text'] . '|' . + $this->_grammar['no-fold-quote'] . ')'; + $this->_grammar['id-right'] = '(?:' . $this->_grammar['dot-atom-text'] . '|' . + $this->_grammar['no-fold-literal'] . ')'; + + //Addresses, mailboxes and paths + $this->_grammar['local-part'] = '(?:' . $this->_grammar['dot-atom'] . '|' . + $this->_grammar['quoted-string'] . ')'; + $this->_grammar['dcontent'] = '(?:' . $this->_grammar['dtext'] . '|' . + $this->_grammar['quoted-pair'] . ')'; + $this->_grammar['domain-literal'] = '(?:' . $this->_grammar['CFWS'] . '?\[(' . + $this->_grammar['FWS'] . '?' . $this->_grammar['dcontent'] . ')*?' . + $this->_grammar['FWS'] . '?\]' . $this->_grammar['CFWS'] . '?)'; + $this->_grammar['domain'] = '(?:' . $this->_grammar['dot-atom'] . '|' . + $this->_grammar['domain-literal'] . ')'; + $this->_grammar['addr-spec'] = '(?:' . $this->_grammar['local-part'] . '@' . + $this->_grammar['domain'] . ')'; + } + + /** + * Get the grammar defined for $name token. + * @param string $name execatly as written in the RFC + * @return string + */ + protected function getGrammar($name) + { + if (array_key_exists($name, $this->_grammar)) + { + return $this->_grammar[$name]; + } + else + { + throw new Swift_RfcComplianceException( + "No such grammar '" . $name . "' defined." + ); + } + } + + /** + * Escape special characters in a string (convert to quoted-pairs). + * @param string $token + * @param string[] $include additonal chars to escape + * @param string[] $exclude chars from escaping + * @return string + */ + protected function escapeSpecials($token, $include = array(), + $exclude = array()) + { + foreach ( + array_merge(array('\\'), array_diff($this->_specials, $exclude), $include) as $char) + { + $token = str_replace($char, '\\' . $char, $token); + } + return $token; + } + + /** + * Produces a compliant, formatted RFC 2822 'phrase' based on the string given. + * @param Swift_Mime_Header $header + * @param string $string as displayed + * @param string $charset of the text + * @param Swift_Mime_HeaderEncoder $encoder + * @param boolean $shorten the first line to make remove for header name + * @return string + */ + protected function createPhrase(Swift_Mime_Header $header, $string, $charset, + Swift_Mime_HeaderEncoder $encoder = null, $shorten = false) + { + //Treat token as exactly what was given + $phraseStr = $string; + //If it's not valid + if (!preg_match('/^' . $this->_grammar['phrase'] . '$/D', $phraseStr)) + { + // .. but it is just ascii text, try escaping some characters + // and make it a quoted-string + if (preg_match('/^' . $this->_grammar['text'] . '*$/D', $phraseStr)) + { + $phraseStr = $this->escapeSpecials( + $phraseStr, array('"'), $this->_specials + ); + $phraseStr = '"' . $phraseStr . '"'; + } + else // ... otherwise it needs encoding + { + //Determine space remaining on line if first line + if ($shorten) + { + $usedLength = strlen($header->getFieldName() . ': '); + } + else + { + $usedLength = 0; + } + $phraseStr = $this->encodeWords($header, $string, $usedLength); + } + } + + return $phraseStr; + } + + /** + * Encode needed word tokens within a string of input. + * @param string $input + * @param string $usedLength, optional + * @return string + */ + protected function encodeWords(Swift_Mime_Header $header, $input, + $usedLength = -1) + { + $value = ''; + + $tokens = $this->getEncodableWordTokens($input); + + foreach ($tokens as $token) + { + //See RFC 2822, Sect 2.2 (really 2.2 ??) + if ($this->tokenNeedsEncoding($token)) + { + //Don't encode starting WSP + $firstChar = substr($token, 0, 1); + switch($firstChar) + { + case ' ': + case "\t": + $value .= $firstChar; + $token = substr($token, 1); + } + + if (-1 == $usedLength) + { + $usedLength = strlen($header->getFieldName() . ': ') + strlen($value); + } + $value .= $this->getTokenAsEncodedWord($token, $usedLength); + + $header->setMaxLineLength(76); //Forefully override + } + else + { + $value .= $token; + } + } + + return $value; + } + + /** + * Test if a token needs to be encoded or not. + * @param string $token + * @return boolean + */ + protected function tokenNeedsEncoding($token) + { + return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); + } + + /** + * Splits a string into tokens in blocks of words which can be encoded quickly. + * @param string $string + * @return string[] + */ + protected function getEncodableWordTokens($string) + { + $tokens = array(); + + $encodedToken = ''; + //Split at all whitespace boundaries + foreach (preg_split('~(?=[\t ])~', $string) as $token) + { + if ($this->tokenNeedsEncoding($token)) + { + $encodedToken .= $token; + } + else + { + if (strlen($encodedToken) > 0) + { + $tokens[] = $encodedToken; + $encodedToken = ''; + } + $tokens[] = $token; + } + } + if (strlen($encodedToken)) + { + $tokens[] = $encodedToken; + } + + return $tokens; + } + + /** + * Get a token as an encoded word for safe insertion into headers. + * @param string $token to encode + * @param int $firstLineOffset, optional + * @return string + */ + protected function getTokenAsEncodedWord($token, $firstLineOffset = 0) + { + //Adjust $firstLineOffset to account for space needed for syntax + $charsetDecl = $this->_charset; + if (isset($this->_lang)) + { + $charsetDecl .= '*' . $this->_lang; + } + $encodingWrapperLength = strlen( + '=?' . $charsetDecl . '?' . $this->_encoder->getName() . '??=' + ); + + if ($firstLineOffset >= 75) //Does this logic need to be here? + { + $firstLineOffset = 0; + } + + $encodedTextLines = explode("\r\n", + $this->_encoder->encodeString( + $token, $firstLineOffset, 75 - $encodingWrapperLength + ) + ); + + foreach ($encodedTextLines as $lineNum => $line) + { + $encodedTextLines[$lineNum] = '=?' . $charsetDecl . + '?' . $this->_encoder->getName() . + '?' . $line . '?='; + } + + return implode("\r\n ", $encodedTextLines); + } + + /** + * Generates tokens from the given string which include CRLF as individual tokens. + * @param string $token + * @return string[] + * @access protected + */ + protected function generateTokenLines($token) + { + return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE); + } + + /** + * Set a value into the cache. + * @param string $value + * @access protected + */ + protected function setCachedValue($value) + { + $this->_cachedValue = $value; + } + + /** + * Get the value in the cache. + * @return string + * @access protected + */ + protected function getCachedValue() + { + return $this->_cachedValue; + } + + /** + * Clear the cached value if $condition is met. + * @param boolean $condition + * @access protected + */ + protected function clearCachedValueIf($condition) + { + if ($condition) + { + $this->setCachedValue(null); + } + } + + // -- Private methods + + /** + * Generate a list of all tokens in the final header. + * @param string $string input, optional + * @return string[] + * @access private + */ + protected function toTokens($string = null) + { + if (is_null($string)) + { + $string = $this->getFieldBody(); + } + + $tokens = array(); + + //Generate atoms; split at all invisible boundaries followed by WSP + foreach (preg_split('~(?=[ \t])~', $string) as $token) + { + $tokens = array_merge($tokens, $this->generateTokenLines($token)); + } + + return $tokens; + } + + /** + * Takes an array of tokens which appear in the header and turns them into + * an RFC 2822 compliant string, adding FWSP where needed. + * @param string[] $tokens + * @return string + * @access private + */ + private function _tokensToString(array $tokens) + { + $lineCount = 0; + $headerLines = array(); + $headerLines[] = $this->_name . ': '; + $currentLine =& $headerLines[$lineCount++]; + + //Build all tokens back into compliant header + foreach ($tokens as $i => $token) + { + //Line longer than specified maximum or token was just a new line + if (("\r\n" == $token) || + ($i > 0 && strlen($currentLine . $token) > $this->_lineLength) + && 0 < strlen($currentLine)) + { + $headerLines[] = ''; + $currentLine =& $headerLines[$lineCount++]; + } + + //Append token to the line + if ("\r\n" != $token) + { + $currentLine .= $token; + } + } + + //Implode with FWS (RFC 2822, 2.2.3) + return implode("\r\n", $headerLines) . "\r\n"; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Headers/DateHeader.php b/lib/Swift/lib/classes/Swift/Mime/Headers/DateHeader.php new file mode 100644 index 0000000..598c0c5 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Headers/DateHeader.php @@ -0,0 +1,118 @@ + + * + * + * @param string $name of Header + */ + public function __construct($name) + { + $this->setFieldName($name); + } + + /** + * Get the type of Header that this instance represents. + * @return int + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + */ + public function getFieldType() + { + return self::TYPE_DATE; + } + + /** + * Set the model for the field body. + * This method takes a UNIX timestamp. + * @param int $model + */ + public function setFieldBodyModel($model) + { + $this->setTimestamp($model); + } + + /** + * Get the model for the field body. + * This method returns a UNIX timestamp. + * @return mixed + */ + public function getFieldBodyModel() + { + return $this->getTimestamp(); + } + + /** + * Get the UNIX timestamp of the Date in this Header. + * @return int + */ + public function getTimestamp() + { + return $this->_timestamp; + } + + /** + * Set the UNIX timestamp of the Date in this Header. + * @param int $timestamp + */ + public function setTimestamp($timestamp) + { + if (!is_null($timestamp)) + { + $timestamp = (int) $timestamp; + } + $this->clearCachedValueIf($this->_timestamp != $timestamp); + $this->_timestamp = $timestamp; + } + + /** + * Get the string value of the body in this Header. + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * @return string + * @see toString() + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) + { + if (isset($this->_timestamp)) + { + $this->setCachedValue(date('r', $this->_timestamp)); + } + } + return $this->getCachedValue(); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Headers/IdentificationHeader.php b/lib/Swift/lib/classes/Swift/Mime/Headers/IdentificationHeader.php new file mode 100644 index 0000000..55ff737 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Headers/IdentificationHeader.php @@ -0,0 +1,161 @@ +setFieldName($name); + $this->initializeGrammar(); + } + + /** + * Get the type of Header that this instance represents. + * @return int + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + */ + public function getFieldType() + { + return self::TYPE_ID; + } + + /** + * Set the model for the field body. + * This method takes a string ID, or an array of IDs + * @param mixed $model + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setId($model); + } + + /** + * Get the model for the field body. + * This method returns an array of IDs + * @return array + */ + public function getFieldBodyModel() + { + return $this->getIds(); + } + + /** + * Set the ID used in the value of this header. + * @param string $id + * @throws Swift_RfcComplianceException + */ + public function setId($id) + { + return $this->setIds(array($id)); + } + + /** + * Get the ID used in the value of this Header. + * If multiple IDs are set only the first is returned. + * @return string + */ + public function getId() + { + if (count($this->_ids) > 0) + { + return $this->_ids[0]; + } + } + + /** + * Set a collection of IDs to use in the value of this Header. + * @param string[] $ids + * @throws Swift_RfcComplianceException + */ + public function setIds(array $ids) + { + $actualIds = array(); + + foreach ($ids as $k => $id) + { + if (preg_match( + '/^' . $this->getGrammar('id-left') . '@' . + $this->getGrammar('id-right') . '$/D', + $id + )) + { + $actualIds[] = $id; + } + else + { + throw new Swift_RfcComplianceException( + 'Invalid ID given <' . $id . '>' + ); + } + } + + $this->clearCachedValueIf($this->_ids != $actualIds); + $this->_ids = $actualIds; + } + + /** + * Get the list of IDs used in this Header. + * @return string[] + */ + public function getIds() + { + return $this->_ids; + } + + /** + * Get the string value of the body in this Header. + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * @return string + * @see toString() + * @throws Swift_RfcComplianceException + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) + { + $angleAddrs = array(); + + foreach ($this->_ids as $id) + { + $angleAddrs[] = '<' . $id . '>'; + } + + $this->setCachedValue(implode(' ', $angleAddrs)); + } + return $this->getCachedValue(); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Headers/MailboxHeader.php b/lib/Swift/lib/classes/Swift/Mime/Headers/MailboxHeader.php new file mode 100644 index 0000000..77d3bba --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Headers/MailboxHeader.php @@ -0,0 +1,316 @@ +setFieldName($name); + $this->setEncoder($encoder); + $this->initializeGrammar(); + } + + /** + * Get the type of Header that this instance represents. + * @return int + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + */ + public function getFieldType() + { + return self::TYPE_MAILBOX; + } + + /** + * Set the model for the field body. + * This method takes a string, or an array of addresses. + * @param mixed $model + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setNameAddresses($model); + } + + /** + * Get the model for the field body. + * This method returns an associative array like {@link getNameAddresses()} + * @return array + * @throws Swift_RfcComplianceException + */ + public function getFieldBodyModel() + { + return $this->getNameAddresses(); + } + + /** + * Set a list of mailboxes to be shown in this Header. + * The mailboxes can be a simple array of addresses, or an array of + * key=>value pairs where (email => personalName). + * Example: + * + * setNameAddresses(array( + * 'chris@swiftmailer.org' => 'Chris Corbyn', + * 'mark@swiftmailer.org' //No associated personal name + * )); + * ?> + * + * @param string|string[] $mailboxes + * @throws Swift_RfcComplianceException + * @see __construct() + * @see setAddresses() + * @see setValue() + */ + public function setNameAddresses($mailboxes) + { + $this->_mailboxes = $this->normalizeMailboxes((array) $mailboxes); + $this->setCachedValue(null); //Clear any cached value + } + + /** + * Get the full mailbox list of this Header as an array of valid RFC 2822 strings. + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddressStrings()); + * // array ( + * // 0 => Chris Corbyn , + * // 1 => Mark Corbyn + * // ) + * ?> + * + * @return string[] + * @throws Swift_RfcComplianceException + * @see getNameAddresses() + * @see toString() + */ + public function getNameAddressStrings() + { + return $this->_createNameAddressStrings($this->getNameAddresses()); + } + + /** + * Get all mailboxes in this Header as key=>value pairs. + * The key is the address and the value is the name (or null if none set). + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddresses()); + * // array ( + * // chris@swiftmailer.org => Chris Corbyn, + * // mark@swiftmailer.org => Mark Corbyn + * // ) + * ?> + * + * @return string[] + * @see getAddresses() + * @see getNameAddressStrings() + */ + public function getNameAddresses() + { + return $this->_mailboxes; + } + + /** + * Makes this Header represent a list of plain email addresses with no names. + * Example: + * + * setAddresses( + * array('one@domain.tld', 'two@domain.tld', 'three@domain.tld') + * ); + * ?> + * + * @param string[] $addresses + * @throws Swift_RfcComplianceException + * @see setNameAddresses() + * @see setValue() + */ + public function setAddresses($addresses) + { + return $this->setNameAddresses(array_values((array) $addresses)); + } + + /** + * Get all email addresses in this Header. + * @return string[] + * @see getNameAddresses() + */ + public function getAddresses() + { + return array_keys($this->_mailboxes); + } + + /** + * Remove one or more addresses from this Header. + * @param string|string[] $addresses + */ + public function removeAddresses($addresses) + { + $this->setCachedValue(null); + foreach ((array) $addresses as $address) + { + unset($this->_mailboxes[$address]); + } + } + + /** + * Get the string value of the body in this Header. + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * @return string + * @throws Swift_RfcComplianceException + * @see toString() + */ + public function getFieldBody() + { + //Compute the string value of the header only if needed + if (is_null($this->getCachedValue())) + { + $this->setCachedValue($this->createMailboxListString($this->_mailboxes)); + } + return $this->getCachedValue(); + } + + // -- Points of extension + + /** + * Normalizes a user-input list of mailboxes into consistent key=>value pairs. + * @param string[] $mailboxes + * @return string[] + * @access protected + */ + protected function normalizeMailboxes(array $mailboxes) + { + $actualMailboxes = array(); + + foreach ($mailboxes as $key => $value) + { + if (is_string($key)) //key is email addr + { + $address = $key; + $name = $value; + } + else + { + $address = $value; + $name = null; + } + $this->_assertValidAddress($address); + $actualMailboxes[$address] = $name; + } + + return $actualMailboxes; + } + + /** + * Produces a compliant, formatted display-name based on the string given. + * @param string $displayName as displayed + * @param boolean $shorten the first line to make remove for header name + * @return string + * @access protected + */ + protected function createDisplayNameString($displayName, $shorten = false) + { + return $this->createPhrase($this, $displayName, + $this->getCharset(), $this->getEncoder(), $shorten + ); + } + + /** + * Creates a string form of all the mailboxes in the passed array. + * @param string[] $mailboxes + * @return string + * @throws Swift_RfcComplianceException + * @access protected + */ + protected function createMailboxListString(array $mailboxes) + { + return implode(', ', $this->_createNameAddressStrings($mailboxes)); + } + + // -- Private methods + + /** + * Return an array of strings conforming the the name-addr spec of RFC 2822. + * @param string[] $mailboxes + * @return string[] + * @access private + */ + private function _createNameAddressStrings(array $mailboxes) + { + $strings = array(); + + foreach ($mailboxes as $email => $name) + { + $mailboxStr = $email; + if (!is_null($name)) + { + $nameStr = $this->createDisplayNameString($name, empty($strings)); + $mailboxStr = $nameStr . ' <' . $mailboxStr . '>'; + } + $strings[] = $mailboxStr; + } + + return $strings; + } + + /** + * Throws an Exception if the address passed does not comply with RFC 2822. + * @param string $address + * @throws Exception If invalid. + * @access protected + */ + private function _assertValidAddress($address) + { + if (!preg_match('/^' . $this->getGrammar('addr-spec') . '$/D', + $address)) + { + throw new Swift_RfcComplianceException( + 'Address in mailbox given [' . $address . + '] does not comply with RFC 2822, 3.6.2.' + ); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php b/lib/Swift/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php new file mode 100644 index 0000000..974b44e --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php @@ -0,0 +1,274 @@ +setFieldName($name); + $this->setEncoder($encoder); + $this->_paramEncoder = $paramEncoder; + $this->initializeGrammar(); + $this->_tokenRe = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)'; + } + + /** + * Get the type of Header that this instance represents. + * @return int + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + */ + public function getFieldType() + { + return self::TYPE_PARAMETERIZED; + } + + /** + * Set the character set used in this Header. + * @param string $charset + */ + public function setCharset($charset) + { + parent::setCharset($charset); + if (isset($this->_paramEncoder)) + { + $this->_paramEncoder->charsetChanged($charset); + } + } + + /** + * Set the value of $parameter. + * @param string $parameter + * @param string $value + */ + public function setParameter($parameter, $value) + { + $this->setParameters(array_merge($this->getParameters(), array($parameter => $value))); + } + + /** + * Get the value of $parameter. + * @return string + */ + public function getParameter($parameter) + { + $params = $this->getParameters(); + return array_key_exists($parameter, $params) + ? $params[$parameter] + : null; + } + + /** + * Set an associative array of parameter names mapped to values. + * @param string[] + */ + public function setParameters(array $parameters) + { + $this->clearCachedValueIf($this->_params != $parameters); + $this->_params = $parameters; + } + + /** + * Returns an associative array of parameter names mapped to values. + * @return string[] + */ + public function getParameters() + { + return $this->_params; + } + + /** + * Get the value of this header prepared for rendering. + * @return string + */ + public function getFieldBody() //TODO: Check caching here + { + $body = parent::getFieldBody(); + foreach ($this->_params as $name => $value) + { + if (!is_null($value)) + { + //Add the parameter + $body .= '; ' . $this->_createParameter($name, $value); + } + } + return $body; + } + + // -- Protected methods + + /** + * Generate a list of all tokens in the final header. + * This doesn't need to be overridden in theory, but it is for implementation + * reasons to prevent potential breakage of attributes. + * @return string[] + * @access protected + */ + protected function toTokens($string = null) + { + $tokens = parent::toTokens(parent::getFieldBody()); + + //Try creating any parameters + foreach ($this->_params as $name => $value) + { + if (!is_null($value)) + { + //Add the semi-colon separator + $tokens[count($tokens)-1] .= ';'; + $tokens = array_merge($tokens, $this->generateTokenLines( + ' ' . $this->_createParameter($name, $value) + )); + } + } + + return $tokens; + } + + // -- Private methods + + /** + * Render a RFC 2047 compliant header parameter from the $name and $value. + * @param string $name + * @param string $value + * @return string + * @access private + */ + private function _createParameter($name, $value) + { + $origValue = $value; + + $encoded = false; + //Allow room for parameter name, indices, "=" and DQUOTEs + $maxValueLength = $this->getMaxLineLength() - strlen($name . '=*N"";') - 1; + $firstLineOffset = 0; + + //If it's not already a valid parameter value... + if (!preg_match('/^' . $this->_tokenRe . '$/D', $value)) + { + //TODO: text, or something else?? + //... and it's not ascii + if (!preg_match('/^' . $this->getGrammar('text') . '*$/D', $value)) + { + $encoded = true; + //Allow space for the indices, charset and language + $maxValueLength = $this->getMaxLineLength() - strlen($name . '*N*="";') - 1; + $firstLineOffset = strlen( + $this->getCharset() . "'" . $this->getLanguage() . "'" + ); + } + } + + //Encode if we need to + if ($encoded || strlen($value) > $maxValueLength) + { + if (isset($this->_paramEncoder)) + { + $value = $this->_paramEncoder->encodeString( + $origValue, $firstLineOffset, $maxValueLength + ); + } + else //We have to go against RFC 2183/2231 in some areas for interoperability + { + $value = $this->getTokenAsEncodedWord($origValue); + $encoded = false; + } + } + + $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value); + + //Need to add indices + if (count($valueLines) > 1) + { + $paramLines = array(); + foreach ($valueLines as $i => $line) + { + $paramLines[] = $name . '*' . $i . + $this->_getEndOfParameterValue($line, $encoded, $i == 0); + } + return implode(";\r\n ", $paramLines); + } + else + { + return $name . $this->_getEndOfParameterValue( + $valueLines[0], $encoded, true + ); + } + } + + /** + * Returns the parameter value from the "=" and beyond. + * @param string $value to append + * @param boolean $encoded + * @param boolean $firstLine + * @return string + * @access private + */ + private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false) + { + if (!preg_match('/^' . $this->_tokenRe . '$/D', $value)) + { + $value = '"' . $value . '"'; + } + $prepend = '='; + if ($encoded) + { + $prepend = '*='; + if ($firstLine) + { + $prepend = '*=' . $this->getCharset() . "'" . $this->getLanguage() . + "'"; + } + } + return $prepend . $value; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Headers/PathHeader.php b/lib/Swift/lib/classes/Swift/Mime/Headers/PathHeader.php new file mode 100644 index 0000000..0a8a100 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Headers/PathHeader.php @@ -0,0 +1,126 @@ +setFieldName($name); + $this->initializeGrammar(); + } + + /** + * Get the type of Header that this instance represents. + * @return int + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + */ + public function getFieldType() + { + return self::TYPE_PATH; + } + + /** + * Set the model for the field body. + * This method takes a string for an address. + * @param string $model + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setAddress($model); + } + + /** + * Get the model for the field body. + * This method returns a string email address. + * @return mixed + */ + public function getFieldBodyModel() + { + return $this->getAddress(); + } + + /** + * Set the Address which should appear in this Header. + * @param string $address + * @throws Swift_RfcComplianceException + */ + public function setAddress($address) + { + if (is_null($address)) + { + $this->_address = null; + } + elseif ('' == $address + || preg_match('/^' . $this->getGrammar('addr-spec') . '$/D', $address)) + { + $this->_address = $address; + } + else + { + throw new Swift_RfcComplianceException( + 'Address set in PathHeader does not comply with addr-spec of RFC 2822.' + ); + } + $this->setCachedValue(null); + } + + /** + * Get the address which is used in this Header (if any). + * Null is returned if no address is set. + * @return string + */ + public function getAddress() + { + return $this->_address; + } + + /** + * Get the string value of the body in this Header. + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * @return string + * @see toString() + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) + { + if (isset($this->_address)) + { + $this->setCachedValue('<' . $this->_address . '>'); + } + } + return $this->getCachedValue(); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php b/lib/Swift/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php new file mode 100644 index 0000000..fdcc21e --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php @@ -0,0 +1,108 @@ +setFieldName($name); + $this->setEncoder($encoder); + } + /** + * Get the type of Header that this instance represents. + * @return int + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + */ + public function getFieldType() + { + return self::TYPE_TEXT; + } + + /** + * Set the model for the field body. + * This method takes a string for the field value. + * @param string $model + */ + public function setFieldBodyModel($model) + { + $this->setValue($model); + } + + /** + * Get the model for the field body. + * This method returns a string. + * @return string + */ + public function getFieldBodyModel() + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + * @return string + */ + public function getValue() + { + return $this->_value; + } + + /** + * Set the (unencoded) value of this header. + * @param string $value + */ + public function setValue($value) + { + $this->clearCachedValueIf($this->_value != $value); + $this->_value = $value; + } + + /** + * Get the value of this header prepared for rendering. + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) + { + $this->setCachedValue( + str_replace('\\', '\\\\', $this->encodeWords( + $this, $this->_value, -1, $this->getCharset(), $this->getEncoder() + )) + ); + } + return $this->getCachedValue(); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/Message.php b/lib/Swift/lib/classes/Swift/Mime/Message.php new file mode 100644 index 0000000..0496c08 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/Message.php @@ -0,0 +1,230 @@ + 'Real Name'). + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $address + * @param string $name optional + */ + public function setSender($address, $name = null); + + /** + * Get the sender address for this message. + * + * This has a higher significance than the From address. + * + * @return string + */ + public function getSender(); + + /** + * Set the From address of this message. + * + * It is permissible for multiple From addresses to be set using an array. + * + * If multiple From addresses are used, you SHOULD set the Sender address and + * according to RFC 2822, MUST set the sender address. + * + * An array can be used if display names are to be provided: i.e. + * array('email@address.com' => 'Real Name'). + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setFrom($addresses, $name = null); + + /** + * Get the From address(es) of this message. + * + * This method always returns an associative array where the keys are the + * addresses. + * + * @return string[] + */ + public function getFrom(); + + /** + * Set the Reply-To address(es). + * + * Any replies from the receiver will be sent to this address. + * + * It is permissible for multiple reply-to addresses to be set using an array. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setReplyTo($addresses, $name = null); + + /** + * Get the Reply-To addresses for this message. + * + * This method always returns an associative array where the keys provide the + * email addresses. + * + * @return string[] + */ + public function getReplyTo(); + + /** + * Set the To address(es). + * + * Recipients set in this field will receive a copy of this message. + * + * This method has the same synopsis as {@link setFrom()} and {@link setCc()}. + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setTo($addresses, $name = null); + + /** + * Get the To addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getTo(); + + /** + * Set the Cc address(es). + * + * Recipients set in this field will receive a 'carbon-copy' of this message. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setCc($addresses, $name = null); + + /** + * Get the Cc addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getCc(); + + /** + * Set the Bcc address(es). + * + * Recipients set in this field will receive a 'blind-carbon-copy' of this + * message. + * + * In other words, they will get the message, but any other recipients of the + * message will have no such knowledge of their receipt of it. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setBcc($addresses, $name = null); + + /** + * Get the Bcc addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getBcc(); + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/MimeEntity.php b/lib/Swift/lib/classes/Swift/Mime/MimeEntity.php new file mode 100644 index 0000000..2b08009 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/MimeEntity.php @@ -0,0 +1,108 @@ +setContentType('text/plain'); + if (!is_null($charset)) + { + $this->setCharset($charset); + } + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * + * @param mixed $body + * @param string $contentType optional + * @param string $charset optional + */ + public function setBody($body, $contentType = null, $charset = null) + { + parent::setBody($body, $contentType); + if (isset($charset)) + { + $this->setCharset($charset); + } + return $this; + } + + /** + * Get the character set of this entity. + * + * @return string + */ + public function getCharset() + { + return $this->_getHeaderParameter('Content-Type', 'charset'); + } + + /** + * Set the character set of this entity. + * + * @param string $charset + */ + public function setCharset($charset) + { + $this->_setHeaderParameter('Content-Type', 'charset', $charset); + if ($charset !== $this->_userCharset) + { + $this->_clearCache(); + } + $this->_userCharset = $charset; + parent::charsetChanged($charset); + return $this; + } + + /** + * Get the format of this entity (i.e. flowed or fixed). + * + * @return string + */ + public function getFormat() + { + return $this->_getHeaderParameter('Content-Type', 'format'); + } + + /** + * Set the format of this entity (flowed or fixed). + * + * @param string $format + */ + public function setFormat($format) + { + $this->_setHeaderParameter('Content-Type', 'format', $format); + $this->_userFormat = $format; + return $this; + } + + /** + * Test if delsp is being used for this entity. + * + * @return boolean + */ + public function getDelSp() + { + return ($this->_getHeaderParameter('Content-Type', 'delsp') == 'yes') + ? true + : false; + } + + /** + * Turn delsp on or off for this entity. + * + * @param boolean $delsp + */ + public function setDelSp($delsp = true) + { + $this->_setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null); + $this->_userDelSp = $delsp; + return $this; + } + + /** + * Get the nesting level of this entity. + * + * @return int + * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED + */ + public function getNestingLevel() + { + return $this->_nestingLevel; + } + + /** + * Receive notification that the charset has changed on this document, or a + * parent document. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + // -- Protected methods + + /** Fix the content-type and encoding of this entity */ + protected function _fixHeaders() + { + parent::_fixHeaders(); + if (count($this->getChildren())) + { + $this->_setHeaderParameter('Content-Type', 'charset', null); + $this->_setHeaderParameter('Content-Type', 'format', null); + $this->_setHeaderParameter('Content-Type', 'delsp', null); + } + else + { + $this->setCharset($this->_userCharset); + $this->setFormat($this->_userFormat); + $this->setDelSp($this->_userDelSp); + } + } + + /** Set the nesting level of this entity */ + protected function _setNestingLevel($level) + { + $this->_nestingLevel = $level; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/ParameterizedHeader.php b/lib/Swift/lib/classes/Swift/Mime/ParameterizedHeader.php new file mode 100644 index 0000000..da65ca9 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/ParameterizedHeader.php @@ -0,0 +1,35 @@ +_encoder = $encoder; + $this->_paramEncoder = $paramEncoder; + $this->_charset = $charset; + } + + /** + * Create a new Mailbox Header with a list of $addresses. + * @param string $name + * @param array|string $addresses + * @return Swift_Mime_Header + */ + public function createMailboxHeader($name, $addresses = null) + { + $header = new Swift_Mime_Headers_MailboxHeader($name, $this->_encoder); + if (isset($addresses)) + { + $header->setFieldBodyModel($addresses); + } + $this->_setHeaderCharset($header); + return $header; + } + + /** + * Create a new Date header using $timestamp (UNIX time). + * @param string $name + * @param int $timestamp + * @return Swift_Mime_Header + */ + public function createDateHeader($name, $timestamp = null) + { + $header = new Swift_Mime_Headers_DateHeader($name); + if (isset($timestamp)) + { + $header->setFieldBodyModel($timestamp); + } + $this->_setHeaderCharset($header); + return $header; + } + + /** + * Create a new basic text header with $name and $value. + * @param string $name + * @param string $value + * @return Swift_Mime_Header + */ + public function createTextHeader($name, $value = null) + { + $header = new Swift_Mime_Headers_UnstructuredHeader($name, $this->_encoder); + if (isset($value)) + { + $header->setFieldBodyModel($value); + } + $this->_setHeaderCharset($header); + return $header; + } + + /** + * Create a new ParameterizedHeader with $name, $value and $params. + * @param string $name + * @param string $value + * @param array $params + * @return Swift_Mime_ParameterizedHeader + */ + public function createParameterizedHeader($name, $value = null, + $params = array()) + { + $header = new Swift_Mime_Headers_ParameterizedHeader($name, + $this->_encoder, (strtolower($name) == 'content-disposition') + ? $this->_paramEncoder + : null + ); + if (isset($value)) + { + $header->setFieldBodyModel($value); + } + foreach ($params as $k => $v) + { + $header->setParameter($k, $v); + } + $this->_setHeaderCharset($header); + return $header; + } + + /** + * Create a new ID header for Message-ID or Content-ID. + * @param string $name + * @param string|array $ids + * @return Swift_Mime_Header + */ + public function createIdHeader($name, $ids = null) + { + $header = new Swift_Mime_Headers_IdentificationHeader($name); + if (isset($ids)) + { + $header->setFieldBodyModel($ids); + } + $this->_setHeaderCharset($header); + return $header; + } + + /** + * Create a new Path header with an address (path) in it. + * @param string $name + * @param string $path + * @return Swift_Mime_Header + */ + public function createPathHeader($name, $path = null) + { + $header = new Swift_Mime_Headers_PathHeader($name); + if (isset($path)) + { + $header->setFieldBodyModel($path); + } + $this->_setHeaderCharset($header); + return $header; + } + + /** + * Notify this observer that the entity's charset has changed. + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_charset = $charset; + $this->_encoder->charsetChanged($charset); + $this->_paramEncoder->charsetChanged($charset); + } + + // -- Private methods + + /** Apply the charset to the Header */ + private function _setHeaderCharset(Swift_Mime_Header $header) + { + if (isset($this->_charset)) + { + $header->setCharset($this->_charset); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/SimpleHeaderSet.php b/lib/Swift/lib/classes/Swift/Mime/SimpleHeaderSet.php new file mode 100644 index 0000000..eeb0221 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/SimpleHeaderSet.php @@ -0,0 +1,396 @@ +_factory = $factory; + if (isset($charset)) + { + $this->setCharset($charset); + } + } + + /** + * Set the charset used by these headers. + * + * @param string $charset + */ + public function setCharset($charset) + { + $this->_charset = $charset; + $this->_factory->charsetChanged($charset); + $this->_notifyHeadersOfCharset($charset); + } + + /** + * Add a new Mailbox Header with a list of $addresses. + * + * @param string $name + * @param array|string $addresses + */ + public function addMailboxHeader($name, $addresses = null) + { + $this->_storeHeader($name, + $this->_factory->createMailboxHeader($name, $addresses)); + } + + /** + * Add a new Date header using $timestamp (UNIX time). + * + * @param string $name + * @param int $timestamp + */ + public function addDateHeader($name, $timestamp = null) + { + $this->_storeHeader($name, + $this->_factory->createDateHeader($name, $timestamp)); + } + + /** + * Add a new basic text header with $name and $value. + * + * @param string $name + * @param string $value + */ + public function addTextHeader($name, $value = null) + { + $this->_storeHeader($name, + $this->_factory->createTextHeader($name, $value)); + } + + /** + * Add a new ParameterizedHeader with $name, $value and $params. + * + * @param string $name + * @param string $value + * @param array $params + */ + public function addParameterizedHeader($name, $value = null, + $params = array()) + { + $this->_storeHeader($name, + $this->_factory->createParameterizedHeader($name, $value, + $params)); + } + + /** + * Add a new ID header for Message-ID or Content-ID. + * + * @param string $name + * @param string|array $ids + */ + public function addIdHeader($name, $ids = null) + { + $this->_storeHeader($name, $this->_factory->createIdHeader($name, $ids)); + } + + /** + * Add a new Path header with an address (path) in it. + * + * @param string $name + * @param string $path + */ + public function addPathHeader($name, $path = null) + { + $this->_storeHeader($name, $this->_factory->createPathHeader($name, $path)); + } + + /** + * Returns true if at least one header with the given $name exists. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param int $index + * + * @return boolean + */ + public function has($name, $index = 0) + { + $lowerName = strtolower($name); + return array_key_exists($lowerName, $this->_headers) + && array_key_exists($index, $this->_headers[$lowerName]); + } + + /** + * Set a header in the HeaderSet. + * + * The header may be a previously fetched header via {@link get()} or it may + * be one that has been created separately. + * + * If $index is specified, the header will be inserted into the set at this + * offset. + * + * @param Swift_Mime_Header $header + * @param int $index + */ + public function set(Swift_Mime_Header $header, $index = 0) + { + $this->_storeHeader($header->getFieldName(), $header, $index); + } + + /** + * Get the header with the given $name. + * + * If multiple headers match, the actual one may be specified by $index. + * Returns NULL if none present. + * + * @param string $name + * @param int $index + * + * @return Swift_Mime_Header + */ + public function get($name, $index = 0) + { + if ($this->has($name, $index)) + { + $lowerName = strtolower($name); + return $this->_headers[$lowerName][$index]; + } + } + + /** + * Get all headers with the given $name. + * + * @param string $name + * + * @return array + */ + public function getAll($name = null) + { + if (!isset($name)) + { + $headers = array(); + foreach ($this->_headers as $collection) + { + $headers = array_merge($headers, $collection); + } + return $headers; + } + + $lowerName = strtolower($name); + if (!array_key_exists($lowerName, $this->_headers)) + { + return array(); + } + return $this->_headers[$lowerName]; + } + + /** + * Remove the header with the given $name if it's set. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param int $index + */ + public function remove($name, $index = 0) + { + $lowerName = strtolower($name); + unset($this->_headers[$lowerName][$index]); + } + + /** + * Remove all headers with the given $name. + * + * @param string $name + */ + public function removeAll($name) + { + $lowerName = strtolower($name); + unset($this->_headers[$lowerName]); + } + + /** + * Create a new instance of this HeaderSet. + * + * @return Swift_Mime_HeaderSet + */ + public function newInstance() + { + return new self($this->_factory); + } + + /** + * Define a list of Header names as an array in the correct order. + * + * These Headers will be output in the given order where present. + * + * @param array $sequence + */ + public function defineOrdering(array $sequence) + { + $this->_order = array_flip(array_map('strtolower', $sequence)); + } + + /** + * Set a list of header names which must always be displayed when set. + * + * Usually headers without a field value won't be output unless set here. + * + * @param array $names + */ + public function setAlwaysDisplayed(array $names) + { + $this->_required = array_flip(array_map('strtolower', $names)); + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + /** + * Returns a string with a representation of all headers. + * + * @return string + */ + public function toString() + { + $string = ''; + $headers = $this->_headers; + if ($this->_canSort()) + { + uksort($headers, array($this, '_sortHeaders')); + } + foreach ($headers as $collection) + { + foreach ($collection as $header) + { + if ($this->_isDisplayed($header) || $header->getFieldBody() != '') + { + $string .= $header->toString(); + } + } + } + return $string; + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + // -- Private methods + + /** Save a Header to the internal collection */ + private function _storeHeader($name, Swift_Mime_Header $header, $offset = null) + { + if (!isset($this->_headers[strtolower($name)])) + { + $this->_headers[strtolower($name)] = array(); + } + if (!isset($offset)) + { + $this->_headers[strtolower($name)][] = $header; + } + else + { + $this->_headers[strtolower($name)][$offset] = $header; + } + } + + /** Test if the headers can be sorted */ + private function _canSort() + { + return count($this->_order) > 0; + } + + /** uksort() algorithm for Header ordering */ + private function _sortHeaders($a, $b) + { + $lowerA = strtolower($a); + $lowerB = strtolower($b); + $aPos = array_key_exists($lowerA, $this->_order) + ? $this->_order[$lowerA] + : -1; + $bPos = array_key_exists($lowerB, $this->_order) + ? $this->_order[$lowerB] + : -1; + + if ($aPos == -1) + { + return 1; + } + elseif ($bPos == -1) + { + return -1; + } + + return ($aPos < $bPos) ? -1 : 1; + } + + /** Test if the given Header is always displayed */ + private function _isDisplayed(Swift_Mime_Header $header) + { + return array_key_exists(strtolower($header->getFieldName()), $this->_required); + } + + /** Notify all Headers of the new charset */ + private function _notifyHeadersOfCharset($charset) + { + foreach ($this->_headers as $headerGroup) + { + foreach ($headerGroup as $header) + { + $header->setCharset($charset); + } + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/SimpleMessage.php b/lib/Swift/lib/classes/Swift/Mime/SimpleMessage.php new file mode 100644 index 0000000..bbe1e8f --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/SimpleMessage.php @@ -0,0 +1,609 @@ +getHeaders()->defineOrdering(array( + 'Return-Path', + 'Sender', + 'Message-ID', + 'Date', + 'Subject', + 'From', + 'Reply-To', + 'To', + 'Cc', + 'Bcc', + 'MIME-Version', + 'Content-Type', + 'Content-Transfer-Encoding' + )); + $this->getHeaders()->setAlwaysDisplayed( + array('Date', 'Message-ID', 'From') + ); + $this->getHeaders()->addTextHeader('MIME-Version', '1.0'); + $this->setDate(time()); + $this->setId($this->getId()); + $this->getHeaders()->addMailboxHeader('From'); + } + + /** + * Always returns {@link LEVEL_TOP} for a message instance. + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_TOP; + } + + /** + * Set the subject of this message. + * @param string $subject + */ + public function setSubject($subject) + { + if (!$this->_setHeaderFieldModel('Subject', $subject)) + { + $this->getHeaders()->addTextHeader('Subject', $subject); + } + return $this; + } + + /** + * Get the subject of this message. + * @return string + */ + public function getSubject() + { + return $this->_getHeaderFieldModel('Subject'); + } + + /** + * Set the date at which this message was created. + * @param int $date + */ + public function setDate($date) + { + if (!$this->_setHeaderFieldModel('Date', $date)) + { + $this->getHeaders()->addDateHeader('Date', $date); + } + return $this; + } + + /** + * Get the date at which this message was created. + * @return int + */ + public function getDate() + { + return $this->_getHeaderFieldModel('Date'); + } + + /** + * Set the return-path (the bounce address) of this message. + * @param string $address + */ + public function setReturnPath($address) + { + if (!$this->_setHeaderFieldModel('Return-Path', $address)) + { + $this->getHeaders()->addPathHeader('Return-Path', $address); + } + return $this; + } + + /** + * Get the return-path (bounce address) of this message. + * @return string + */ + public function getReturnPath() + { + return $this->_getHeaderFieldModel('Return-Path'); + } + + /** + * Set the sender of this message. + * This does not override the From field, but it has a higher significance. + * @param string $sender + * @param string $name optional + */ + public function setSender($address, $name = null) + { + if (!is_array($address) && isset($name)) + { + $address = array($address => $name); + } + + if (!$this->_setHeaderFieldModel('Sender', (array) $address)) + { + $this->getHeaders()->addMailboxHeader('Sender', (array) $address); + } + return $this; + } + + /** + * Get the sender of this message. + * @return string + */ + public function getSender() + { + return $this->_getHeaderFieldModel('Sender'); + } + + /** + * Add a From: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + */ + public function addFrom($address, $name = null) + { + $current = $this->getFrom(); + $current[$address] = $name; + return $this->setFrom($current); + } + + /** + * Set the from address of this message. + * + * You may pass an array of addresses if this message is from multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param string $addresses + * @param string $name optional + */ + public function setFrom($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) + { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('From', (array) $addresses)) + { + $this->getHeaders()->addMailboxHeader('From', (array) $addresses); + } + return $this; + } + + /** + * Get the from address of this message. + * + * @return string + */ + public function getFrom() + { + return $this->_getHeaderFieldModel('From'); + } + + /** + * Add a Reply-To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + */ + public function addReplyTo($address, $name = null) + { + $current = $this->getReplyTo(); + $current[$address] = $name; + return $this->setReplyTo($current); + } + + /** + * Set the reply-to address of this message. + * + * You may pass an array of addresses if replies will go to multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param string $addresses + * @param string $name optional + */ + public function setReplyTo($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) + { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Reply-To', (array) $addresses)) + { + $this->getHeaders()->addMailboxHeader('Reply-To', (array) $addresses); + } + return $this; + } + + /** + * Get the reply-to address of this message. + * + * @return string + */ + public function getReplyTo() + { + return $this->_getHeaderFieldModel('Reply-To'); + } + + /** + * Add a To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + */ + public function addTo($address, $name = null) + { + $current = $this->getTo(); + $current[$address] = $name; + return $this->setTo($current); + } + + /** + * Set the to addresses of this message. + * + * If multiple recipients will receive the message and array should be used. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param array $addresses + * @param string $name optional + */ + public function setTo($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) + { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('To', (array) $addresses)) + { + $this->getHeaders()->addMailboxHeader('To', (array) $addresses); + } + return $this; + } + + /** + * Get the To addresses of this message. + * + * @return array + */ + public function getTo() + { + return $this->_getHeaderFieldModel('To'); + } + + /** + * Add a Cc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + */ + public function addCc($address, $name = null) + { + $current = $this->getCc(); + $current[$address] = $name; + return $this->setCc($current); + } + + /** + * Set the Cc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param array $addresses + * @param string $name optional + */ + public function setCc($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) + { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Cc', (array) $addresses)) + { + $this->getHeaders()->addMailboxHeader('Cc', (array) $addresses); + } + return $this; + } + + /** + * Get the Cc address of this message. + * + * @return array + */ + public function getCc() + { + return $this->_getHeaderFieldModel('Cc'); + } + + /** + * Add a Bcc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + */ + public function addBcc($address, $name = null) + { + $current = $this->getBcc(); + $current[$address] = $name; + return $this->setBcc($current); + } + + /** + * Set the Bcc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param array $addresses + * @param string $name optional + */ + public function setBcc($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) + { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Bcc', (array) $addresses)) + { + $this->getHeaders()->addMailboxHeader('Bcc', (array) $addresses); + } + return $this; + } + + /** + * Get the Bcc addresses of this message. + * + * @return array + */ + public function getBcc() + { + return $this->_getHeaderFieldModel('Bcc'); + } + + /** + * Set the priority of this message. + * The value is an integer where 1 is the highest priority and 5 is the lowest. + * @param int $priority + */ + public function setPriority($priority) + { + $priorityMap = array( + 1 => 'Highest', + 2 => 'High', + 3 => 'Normal', + 4 => 'Low', + 5 => 'Lowest' + ); + $pMapKeys = array_keys($priorityMap); + if ($priority > max($pMapKeys)) + { + $priority = max($pMapKeys); + } + elseif ($priority < min($pMapKeys)) + { + $priority = min($pMapKeys); + } + if (!$this->_setHeaderFieldModel('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority]))) + { + $this->getHeaders()->addTextHeader('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority])); + } + return $this; + } + + /** + * Get the priority of this message. + * The returned value is an integer where 1 is the highest priority and 5 + * is the lowest. + * @return int + */ + public function getPriority() + { + list($priority) = sscanf($this->_getHeaderFieldModel('X-Priority'), + '%[1-5]' + ); + return isset($priority) ? $priority : 3; + } + + /** + * Ask for a delivery receipt from the recipient to be sent to $addresses + * @param array $addresses + */ + public function setReadReceiptTo($addresses) + { + if (!$this->_setHeaderFieldModel('Disposition-Notification-To', $addresses)) + { + $this->getHeaders() + ->addMailboxHeader('Disposition-Notification-To', $addresses); + } + return $this; + } + + /** + * Get the addresses to which a read-receipt will be sent. + * @return string + */ + public function getReadReceiptTo() + { + return $this->_getHeaderFieldModel('Disposition-Notification-To'); + } + + /** + * Attach a {@link Swift_Mime_MimeEntity} such as an Attachment or MimePart. + * @param Swift_Mime_MimeEntity $entity + */ + public function attach(Swift_Mime_MimeEntity $entity) + { + $this->setChildren(array_merge($this->getChildren(), array($entity))); + return $this; + } + + /** + * Remove an already attached entity. + * @param Swift_Mime_MimeEntity $entity + */ + public function detach(Swift_Mime_MimeEntity $entity) + { + $newChildren = array(); + foreach ($this->getChildren() as $child) + { + if ($entity !== $child) + { + $newChildren[] = $child; + } + } + $this->setChildren($newChildren); + return $this; + } + + /** + * Attach a {@link Swift_Mime_MimeEntity} and return it's CID source. + * This method should be used when embedding images or other data in a message. + * @param Swift_Mime_MimeEntity $entity + * @return string + */ + public function embed(Swift_Mime_MimeEntity $entity) + { + $this->attach($entity); + return 'cid:' . $entity->getId(); + } + + /** + * Get this message as a complete string. + * @return string + */ + public function toString() + { + if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') + { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $children)); + $string = parent::toString(); + $this->setChildren($children); + } + else + { + $string = parent::toString(); + } + return $string; + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this message to a {@link Swift_InputByteStream}. + * @param Swift_InputByteStream $is + */ + public function toByteStream(Swift_InputByteStream $is) + { + if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') + { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $children)); + parent::toByteStream($is); + $this->setChildren($children); + } + else + { + parent::toByteStream($is); + } + } + + // -- Protected methods + + /** @see Swift_Mime_SimpleMimeEntity::_getIdField() */ + protected function _getIdField() + { + return 'Message-ID'; + } + + // -- Private methods + + /** Turn the body of this message into a child of itself if needed */ + private function _becomeMimePart() + { + $part = new parent($this->getHeaders()->newInstance(), $this->getEncoder(), + $this->_getCache(), $this->_userCharset + ); + $part->setContentType($this->_userContentType); + $part->setBody($this->getBody()); + $part->setFormat($this->_userFormat); + $part->setDelSp($this->_userDelSp); + $part->_setNestingLevel($this->_getTopNestingLevel()); + return $part; + } + + /** Get the highest nesting level nested inside this message */ + private function _getTopNestingLevel() + { + $highestLevel = $this->getNestingLevel(); + foreach ($this->getChildren() as $child) + { + $childLevel = $child->getNestingLevel(); + if ($highestLevel < $childLevel) + { + $highestLevel = $childLevel; + } + } + return $highestLevel; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Mime/SimpleMimeEntity.php b/lib/Swift/lib/classes/Swift/Mime/SimpleMimeEntity.php new file mode 100644 index 0000000..1615822 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Mime/SimpleMimeEntity.php @@ -0,0 +1,803 @@ + array(self::LEVEL_TOP, self::LEVEL_MIXED), + 'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE), + 'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED) + ); + + /** A set of filter rules to define what level an entity should be nested at */ + private $_compoundLevelFilters = array(); + + /** The nesting level of this entity */ + private $_nestingLevel = self::LEVEL_ALTERNATIVE; + + /** A KeyCache instance used during encoding and streaming */ + private $_cache; + + /** Direct descendants of this entity */ + private $_immediateChildren = array(); + + /** All descendants of this entity */ + private $_children = array(); + + /** The maximum line length of the body of this entity */ + private $_maxLineLength = 78; + + /** The order in which alternative mime types should appear */ + private $_alternativePartOrder = array( + 'text/plain' => 1, + 'text/html' => 2, + 'multipart/related' => 3 + ); + + /** The CID of this entity */ + private $_id; + + /** The key used for accessing the cache */ + private $_cacheKey; + + protected $_userContentType; + + /** + * Create a new SimpleMimeEntity with $headers, $encoder and $cache. + * @param Swift_Mime_HeaderSet $headers + * @param Swift_Mime_ContentEncoder $encoder + * @param Swift_KeyCache $cache + */ + public function __construct(Swift_Mime_HeaderSet $headers, + Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache) + { + $this->_cacheKey = uniqid(); + $this->_cache = $cache; + $this->_headers = $headers; + $this->setEncoder($encoder); + $this->_headers->defineOrdering( + array('Content-Type', 'Content-Transfer-Encoding') + ); + + // This array specifies that, when the entire MIME document contains + // $compoundLevel, then for each child within $level, if its Content-Type + // is $contentType then it should be treated as if it's level is + // $neededLevel instead. I tried to write that unambiguously! :-\ + // Data Structure: + // array ( + // $compoundLevel => array( + // $level => array( + // $contentType => $neededLevel + // ) + // ) + // ) + + $this->_compoundLevelFilters = array( + (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array( + self::LEVEL_ALTERNATIVE => array( + 'text/plain' => self::LEVEL_ALTERNATIVE, + 'text/html' => self::LEVEL_RELATED + ) + ) + ); + + $this->_id = $this->getRandomId(); + } + + /** + * Generate a new Content-ID or Message-ID for this MIME entity. + * @return string + */ + public function generateId() + { + $this->setId($this->getRandomId()); + return $this->_id; + } + + /** + * Get the {@link Swift_Mime_HeaderSet} for this entity. + * @return Swift_Mime_HeaderSet + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Get the nesting level of this entity. + * @return int + * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE + */ + public function getNestingLevel() + { + return $this->_nestingLevel; + } + + /** + * Get the Content-type of this entity. + * @return string + */ + public function getContentType() + { + return $this->_getHeaderFieldModel('Content-Type'); + } + + /** + * Set the Content-type of this entity. + * @param string $type + */ + public function setContentType($type) + { + $this->_setContentTypeInHeaders($type); + // Keep track of the value so that if the content-type changes automatically + // due to added child entities, it can be restored if they are later removed + $this->_userContentType = $type; + return $this; + } + + /** + * Get the CID of this entity. + * The CID will only be present in headers if a Content-ID header is present. + * @return string + */ + public function getId() + { + return $this->_headers->has($this->_getIdField()) + ? current((array) $this->_getHeaderFieldModel($this->_getIdField())) + : $this->_id; + } + + /** + * Set the CID of this entity. + * @param string $id + */ + public function setId($id) + { + if (!$this->_setHeaderFieldModel($this->_getIdField(), $id)) + { + $this->_headers->addIdHeader($this->_getIdField(), $id); + } + $this->_id = $id; + return $this; + } + + /** + * Get the description of this entity. + * This value comes from the Content-Description header if set. + * @return string + */ + public function getDescription() + { + return $this->_getHeaderFieldModel('Content-Description'); + } + + /** + * Set the description of this entity. + * This method sets a value in the Content-ID header. + * @param string $description + */ + public function setDescription($description) + { + if (!$this->_setHeaderFieldModel('Content-Description', $description)) + { + $this->_headers->addTextHeader('Content-Description', $description); + } + return $this; + } + + /** + * Get the maximum line length of the body of this entity. + * @return int + */ + public function getMaxLineLength() + { + return $this->_maxLineLength; + } + + /** + * Set the maximum line length of lines in this body. + * Though not enforced by the library, lines should not exceed 1000 chars. + * @param int $length + */ + public function setMaxLineLength($length) + { + $this->_maxLineLength = $length; + return $this; + } + + /** + * Get all children added to this entity. + * @return array of Swift_Mime_Entity + */ + public function getChildren() + { + return $this->_children; + } + + /** + * Set all children of this entity. + * @param array $children Swiift_Mime_Entity instances + * @param int $compoundLevel For internal use only + */ + public function setChildren(array $children, $compoundLevel = null) + { + //TODO: Try to refactor this logic + + $compoundLevel = isset($compoundLevel) + ? $compoundLevel + : $this->_getCompoundLevel($children) + ; + + $immediateChildren = array(); + $grandchildren = array(); + $newContentType = $this->_userContentType; + + foreach ($children as $child) + { + $level = $this->_getNeededChildLevel($child, $compoundLevel); + if (empty($immediateChildren)) //first iteration + { + $immediateChildren = array($child); + } + else + { + $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); + if ($nextLevel == $level) + { + $immediateChildren[] = $child; + } + elseif ($level < $nextLevel) + { + //Re-assign immediateChildren to grandchilden + $grandchildren = array_merge($grandchildren, $immediateChildren); + //Set new children + $immediateChildren = array($child); + } + else + { + $grandchildren[] = $child; + } + } + } + + if (!empty($immediateChildren)) + { + $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); + + //Determine which composite media type is needed to accomodate the + // immediate children + foreach ($this->_compositeRanges as $mediaType => $range) + { + if ($lowestLevel > $range[0] + && $lowestLevel <= $range[1]) + { + $newContentType = $mediaType; + break; + } + } + + //Put any grandchildren in a subpart + if (!empty($grandchildren)) + { + $subentity = $this->_createChild(); + $subentity->_setNestingLevel($lowestLevel); + $subentity->setChildren($grandchildren, $compoundLevel); + array_unshift($immediateChildren, $subentity); + } + } + + $this->_immediateChildren = $immediateChildren; + $this->_children = $children; + $this->_setContentTypeInHeaders($newContentType); + $this->_fixHeaders(); + $this->_sortChildren(); + + return $this; + } + + /** + * Get the body of this entity as a string. + * @return string + */ + public function getBody() + { + return ($this->_body instanceof Swift_OutputByteStream) + ? $this->_readStream($this->_body) + : $this->_body; + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * @param mixed $body + * @param string $contentType optional + */ + public function setBody($body, $contentType = null) + { + if ($body !== $this->_body) + { + $this->_clearCache(); + } + + $this->_body = $body; + if (isset($contentType)) + { + $this->setContentType($contentType); + } + return $this; + } + + /** + * Get the encoder used for the body of this entity. + * @return Swift_Mime_ContentEncoder + */ + public function getEncoder() + { + return $this->_encoder; + } + + /** + * Set the encoder used for the body of this entity. + * @param Swift_Mime_ContentEncoder $encoder + */ + public function setEncoder(Swift_Mime_ContentEncoder $encoder) + { + if ($encoder !== $this->_encoder) + { + $this->_clearCache(); + } + + $this->_encoder = $encoder; + $this->_setEncoding($encoder->getName()); + $this->_notifyEncoderChanged($encoder); + return $this; + } + + /** + * Get the boundary used to separate children in this entity. + * @return string + */ + public function getBoundary() + { + if (!isset($this->_boundary)) + { + $this->_boundary = '_=_swift_v4_' . time() . uniqid() . '_=_'; + } + return $this->_boundary; + } + + /** + * Set the boundary used to separate children in this entity. + * @param string $boundary + * @throws Swift_RfcComplianceException + */ + public function setBoundary($boundary) + { + $this->_assertValidBoundary($boundary); + $this->_boundary = $boundary; + return $this; + } + + /** + * Receive notification that the charset of this entity, or a parent entity + * has changed. + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_notifyCharsetChanged($charset); + } + + /** + * Receive notification that the encoder of this entity or a parent entity + * has changed. + * @param Swift_Mime_ContentEncoder $encoder + */ + public function encoderChanged(Swift_Mime_ContentEncoder $encoder) + { + $this->_notifyEncoderChanged($encoder); + } + + /** + * Get this entire entity as a string. + * @return string + */ + public function toString() + { + $string = $this->_headers->toString(); + if (isset($this->_body) && empty($this->_immediateChildren)) + { + if ($this->_cache->hasKey($this->_cacheKey, 'body')) + { + $body = $this->_cache->getString($this->_cacheKey, 'body'); + } + else + { + $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0, + $this->getMaxLineLength() + ); + $this->_cache->setString($this->_cacheKey, 'body', $body, + Swift_KeyCache::MODE_WRITE + ); + } + $string .= $body; + } + + if (!empty($this->_immediateChildren)) + { + foreach ($this->_immediateChildren as $child) + { + $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n"; + $string .= $child->toString(); + } + $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n"; + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this entire entity to a {@link Swift_InputByteStream}. + * @param Swift_InputByteStream + */ + public function toByteStream(Swift_InputByteStream $is) + { + $is->write($this->_headers->toString()); + $is->commit(); + + if (empty($this->_immediateChildren)) + { + if (isset($this->_body)) + { + if ($this->_cache->hasKey($this->_cacheKey, 'body')) + { + $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is); + } + else + { + $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body'); + if ($cacheIs) + { + $is->bind($cacheIs); + } + + $is->write("\r\n"); + + if ($this->_body instanceof Swift_OutputByteStream) + { + $this->_body->setReadPointer(0); + + $this->_encoder->encodeByteStream($this->_body, $is, 0, + $this->getMaxLineLength() + ); + } + else + { + $is->write($this->_encoder->encodeString( + $this->getBody(), 0, $this->getMaxLineLength() + )); + } + + if ($cacheIs) + { + $is->unbind($cacheIs); + } + } + } + } + + if (!empty($this->_immediateChildren)) + { + foreach ($this->_immediateChildren as $child) + { + $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n"); + $child->toByteStream($is); + } + $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n"); + } + } + + // -- Protected methods + + /** + * Get the name of the header that provides the ID of this entity */ + protected function _getIdField() + { + return 'Content-ID'; + } + + /** + * Get the model data (usually an array or a string) for $field. + */ + protected function _getHeaderFieldModel($field) + { + if ($this->_headers->has($field)) + { + return $this->_headers->get($field)->getFieldBodyModel(); + } + } + + /** + * Set the model data for $field. + */ + protected function _setHeaderFieldModel($field, $model) + { + if ($this->_headers->has($field)) + { + $this->_headers->get($field)->setFieldBodyModel($model); + return true; + } + else + { + return false; + } + } + + /** + * Get the parameter value of $parameter on $field header. + */ + protected function _getHeaderParameter($field, $parameter) + { + if ($this->_headers->has($field)) + { + return $this->_headers->get($field)->getParameter($parameter); + } + } + + /** + * Set the parameter value of $parameter on $field header. + */ + protected function _setHeaderParameter($field, $parameter, $value) + { + if ($this->_headers->has($field)) + { + $this->_headers->get($field)->setParameter($parameter, $value); + return true; + } + else + { + return false; + } + } + + /** + * Re-evaluate what content type and encoding should be used on this entity. + */ + protected function _fixHeaders() + { + if (count($this->_immediateChildren)) + { + $this->_setHeaderParameter('Content-Type', 'boundary', + $this->getBoundary() + ); + $this->_headers->remove('Content-Transfer-Encoding'); + } + else + { + $this->_setHeaderParameter('Content-Type', 'boundary', null); + $this->_setEncoding($this->_encoder->getName()); + } + } + + /** + * Get the KeyCache used in this entity. + */ + protected function _getCache() + { + return $this->_cache; + } + + /** + * Empty the KeyCache for this entity. + */ + protected function _clearCache() + { + $this->_cache->clearKey($this->_cacheKey, 'body'); + } + + /** + * Returns a random Content-ID or Message-ID. + * @return string + */ + protected function getRandomId() + { + $idLeft = time() . '.' . uniqid(); + $idRight = !empty($_SERVER['SERVER_NAME']) + ? $_SERVER['SERVER_NAME'] + : 'swift.generated'; + return $idLeft . '@' . $idRight; + } + + // -- Private methods + + private function _readStream(Swift_OutputByteStream $os) + { + $string = ''; + while (false !== $bytes = $os->read(8192)) + { + $string .= $bytes; + } + return $string; + } + + private function _setEncoding($encoding) + { + if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) + { + $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding); + } + } + + private function _assertValidBoundary($boundary) + { + if (!preg_match( + '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', + $boundary)) + { + throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.'); + } + } + + private function _setContentTypeInHeaders($type) + { + if (!$this->_setHeaderFieldModel('Content-Type', $type)) + { + $this->_headers->addParameterizedHeader('Content-Type', $type); + } + } + + private function _setNestingLevel($level) + { + $this->_nestingLevel = $level; + } + + private function _getCompoundLevel($children) + { + $level = 0; + foreach ($children as $child) + { + $level |= $child->getNestingLevel(); + } + return $level; + } + + private function _getNeededChildLevel($child, $compoundLevel) + { + $filter = array(); + foreach ($this->_compoundLevelFilters as $bitmask => $rules) + { + if (($compoundLevel & $bitmask) === $bitmask) + { + $filter = $rules + $filter; + } + } + + $realLevel = $child->getNestingLevel(); + $lowercaseType = strtolower($child->getContentType()); + + if (isset($filter[$realLevel]) + && isset($filter[$realLevel][$lowercaseType])) + { + return $filter[$realLevel][$lowercaseType]; + } + else + { + return $realLevel; + } + } + + private function _createChild() + { + return new self($this->_headers->newInstance(), + $this->_encoder, $this->_cache); + } + + private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder) + { + foreach ($this->_immediateChildren as $child) + { + $child->encoderChanged($encoder); + } + } + + private function _notifyCharsetChanged($charset) + { + $this->_encoder->charsetChanged($charset); + $this->_headers->charsetChanged($charset); + foreach ($this->_immediateChildren as $child) + { + $child->charsetChanged($charset); + } + } + + private function _sortChildren() + { + $shouldSort = false; + foreach ($this->_immediateChildren as $child) + { + //NOTE: This include alternative parts moved into a related part + if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE) + { + $shouldSort = true; + break; + } + } + + //Sort in order of preference, if there is one + if ($shouldSort) + { + usort($this->_immediateChildren, array($this, '_childSortAlgorithm')); + } + } + + private function _childSortAlgorithm($a, $b) + { + $typePrefs = array(); + $types = array( + strtolower($a->getContentType()), + strtolower($b->getContentType()) + ); + foreach ($types as $type) + { + $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder)) + ? $this->_alternativePartOrder[$type] + : (max($this->_alternativePartOrder) + 1); + } + return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1; + } + + // -- Destructor + + /** + * Empties it's own contents from the cache. + */ + public function __destruct() + { + $this->_cache->clearAll($this->_cacheKey); + } + +} diff --git a/lib/Swift/lib/classes/Swift/MimePart.php b/lib/Swift/lib/classes/Swift/MimePart.php new file mode 100644 index 0000000..60b6d56 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/MimePart.php @@ -0,0 +1,65 @@ +createDependenciesFor('mime.part') + ); + + if (!isset($charset)) + { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) + { + $this->setContentType($contentType); + } + } + + /** + * Create a new MimePart. + * @param string $body + * @param string $contentType + * @param string $charset + * @return Swift_Mime_MimePart + */ + public static function newInstance($body = null, $contentType = null, + $charset = null) + { + return new self($body, $contentType, $charset); + } + +} diff --git a/lib/Swift/lib/classes/Swift/OutputByteStream.php b/lib/Swift/lib/classes/Swift/OutputByteStream.php new file mode 100644 index 0000000..951b838 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/OutputByteStream.php @@ -0,0 +1,41 @@ +setThreshold($threshold); + $this->setSleepTime($sleep); + $this->_sleeper = $sleeper; + } + + /** + * Set the number of emails to send before restarting. + * @param int $threshold + */ + public function setThreshold($threshold) + { + $this->_threshold = $threshold; + } + + /** + * Get the number of emails to send before restarting. + * @return int + */ + public function getThreshold() + { + return $this->_threshold; + } + + /** + * Set the number of seconds to sleep for during a restart. + * @param int $sleep time + */ + public function setSleepTime($sleep) + { + $this->_sleep = $sleep; + } + + /** + * Get the number of seconds to sleep for during a restart. + * @return int + */ + public function getSleepTime() + { + return $this->_sleep; + } + + /** + * Invoked immediately before the Message is sent. + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + ++$this->_counter; + if ($this->_counter >= $this->_threshold) + { + $transport = $evt->getTransport(); + $transport->stop(); + if ($this->_sleep) + { + $this->sleep($this->_sleep); + } + $transport->start(); + $this->_counter = 0; + } + } + + /** + * Sleep for $seconds. + * @param int $seconds + */ + public function sleep($seconds) + { + if (isset($this->_sleeper)) + { + $this->_sleeper->sleep($seconds); + } + else + { + sleep($seconds); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php b/lib/Swift/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php new file mode 100644 index 0000000..501cd80 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php @@ -0,0 +1,173 @@ +getMessage(); + $message->toByteStream($this); + } + + /** + * Invoked immediately following a command being sent. + * @param Swift_Events_ResponseEvent $evt + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->_out += strlen($command); + } + + /** + * Invoked immediately following a response coming back. + * @param Swift_Events_ResponseEvent $evt + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->_in += strlen($response); + } + + /** + * Called when a message is sent so that the outgoing counter can be increased. + * @param string $bytes + */ + public function write($bytes) + { + $this->_out += strlen($bytes); + foreach ($this->_mirrors as $stream) + { + $stream->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Attach $is to this stream. + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + */ + public function bind(Swift_InputByteStream $is) + { + $this->_mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->_mirrors as $k => $stream) + { + if ($is === $stream) + { + unset($this->_mirrors[$k]); + } + } + } + + /** + * Not used. + */ + public function flushBuffers() + { + foreach ($this->_mirrors as $stream) + { + $stream->flushBuffers(); + } + } + + /** + * Get the total number of bytes sent to the server. + * @return int + */ + public function getBytesOut() + { + return $this->_out; + } + + /** + * Get the total number of bytes received from the server. + * @return int + */ + public function getBytesIn() + { + return $this->_in; + } + + /** + * Reset the internal counters to zero. + */ + public function reset() + { + $this->_out = 0; + $this->_in = 0; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Decorator/Replacements.php b/lib/Swift/lib/classes/Swift/Plugins/Decorator/Replacements.php new file mode 100644 index 0000000..9735d0a --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Decorator/Replacements.php @@ -0,0 +1,36 @@ + + * $replacements = array( + * "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"), + * "address2@domain.tld" => array("{a}" => "x", "{c}" => "y") + * ) + * + * + * When using an instance of {@link Swift_Plugins_Decorator_Replacements}, + * the object should return just the array of replacements for the address + * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}. + * + * @param mixed $replacements + */ + public function __construct($replacements) + { + if (!($replacements instanceof Swift_Plugins_Decorator_Replacements)) + { + $this->_replacements = (array) $replacements; + } + else + { + $this->_replacements = $replacements; + } + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $this->_restoreMessage($message); + $to = array_keys($message->getTo()); + $address = array_shift($to); + if ($replacements = $this->getReplacementsFor($address)) + { + $body = $message->getBody(); + $search = array_keys($replacements); + $replace = array_values($replacements); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) + { + $this->_originalBody = $body; + $message->setBody($bodyReplaced); + } + $subject = $message->getSubject(); + $subjectReplaced = str_replace( + $search, $replace, $subject + ); + if ($subject != $subjectReplaced) + { + $this->_originalSubject = $subject; + $message->setSubject($subjectReplaced); + } + $children = (array) $message->getChildren(); + foreach ($children as $child) + { + list($type, ) = sscanf($child->getContentType(), '%[^/]/%s'); + if ('text' == $type) + { + $body = $child->getBody(); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) + { + $child->setBody($bodyReplaced); + $this->_originalChildBodies[$child->getId()] = $body; + } + } + } + $this->_lastMessage = $message; + } + } + + /** + * Find a map of replacements for the address. + * + * If this plugin was provided with a delegate instance of + * {@link Swift_Plugins_Decorator_Replacements} then the call will be + * delegated to it. Otherwise, it will attempt to find the replacements + * from the array provided in the constructor. + * + * If no replacements can be found, an empty value (NULL) is returned. + * + * @param string $address + * + * @return array + */ + public function getReplacementsFor($address) + { + if ($this->_replacements instanceof Swift_Plugins_Decorator_Replacements) + { + return $this->_replacements->getReplacementsFor($address); + } + else + { + return isset($this->_replacements[$address]) + ? $this->_replacements[$address] + : null + ; + } + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $this->_restoreMessage($evt->getMessage()); + } + + // -- Private methods + + /** Restore a changed message back to its original state */ + private function _restoreMessage(Swift_Mime_Message $message) + { + if ($this->_lastMessage === $message) + { + if (isset($this->_originalBody)) + { + $message->setBody($this->_originalBody); + $this->_originalBody = null; + } + if (isset($this->_originalSubject)) + { + $message->setSubject($this->_originalSubject); + $this->_originalSubject = null; + } + if (!empty($this->_originalChildBodies)) + { + $children = (array) $message->getChildren(); + foreach ($children as $child) + { + $id = $child->getId(); + if (array_key_exists($id, $this->_originalChildBodies)) + { + $child->setBody($this->_originalChildBodies[$id]); + } + } + $this->_originalChildBodies = array(); + } + $this->_lastMessage = null; + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Logger.php b/lib/Swift/lib/classes/Swift/Plugins/Logger.php new file mode 100644 index 0000000..9864da0 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Logger.php @@ -0,0 +1,37 @@ +_logger = $logger; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + $this->_logger->add($entry); + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->_logger->clear(); + } + + /** + * Get this log as a string. + * + * @return string + */ + public function dump() + { + return $this->_logger->dump(); + } + + /** + * Invoked immediately following a command being sent. + * + * @param Swift_Events_ResponseEvent $evt + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->_logger->add(sprintf(">> %s", $command)); + } + + /** + * Invoked immediately following a response coming back. + * + * @param Swift_Events_ResponseEvent $evt + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->_logger->add(sprintf("<< %s", $response)); + } + + /** + * Invoked just before a Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ Starting %s", $transportName)); + } + + /** + * Invoked immediately after the Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ %s started", $transportName)); + } + + /** + * Invoked just before a Transport is stopped. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ Stopping %s", $transportName)); + } + + /** + * Invoked immediately after the Transport is stopped. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ %s stopped", $transportName)); + } + + /** + * Invoked as a TransportException is thrown in the Transport system. + * + * @param Swift_Events_TransportExceptionEvent $evt + */ + public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt) + { + $e = $evt->getException(); + $message = $e->getMessage(); + $this->_logger->add(sprintf("!! %s", $message)); + $message .= PHP_EOL; + $message .= 'Log data:' . PHP_EOL; + $message .= $this->_logger->dump(); + $evt->cancelBubble(); + throw new Swift_TransportException($message); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php b/lib/Swift/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php new file mode 100644 index 0000000..930eca2 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php @@ -0,0 +1,73 @@ +_size = $size; + } + + /** + * Add a log entry. + * @param string $entry + */ + public function add($entry) + { + $this->_log[] = $entry; + while (count($this->_log) > $this->_size) + { + array_shift($this->_log); + } + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->_log = array(); + } + + /** + * Get this log as a string. + * @return string + */ + public function dump() + { + return implode(PHP_EOL, $this->_log); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Loggers/EchoLogger.php b/lib/Swift/lib/classes/Swift/Plugins/Loggers/EchoLogger.php new file mode 100644 index 0000000..83dd54b --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Loggers/EchoLogger.php @@ -0,0 +1,64 @@ +_isHtml = $isHtml; + } + + /** + * Add a log entry. + * @param string $entry + */ + public function add($entry) + { + if ($this->_isHtml) + { + printf('%s%s%s', htmlspecialchars($entry, ENT_QUOTES), '
', PHP_EOL); + } + else + { + printf('%s%s', $entry, PHP_EOL); + } + } + + /** + * Not implemented. + */ + public function clear() + { + } + + /** + * Not implemented. + */ + public function dump() + { + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Pop/Pop3Connection.php b/lib/Swift/lib/classes/Swift/Plugins/Pop/Pop3Connection.php new file mode 100644 index 0000000..1c96dcf --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Pop/Pop3Connection.php @@ -0,0 +1,36 @@ +_host = $host; + $this->_port = $port; + $this->_crypto = $crypto; + } + + /** + * Create a new PopBeforeSmtpPlugin for $host and $port. + * + * @param string $host + * @param int $port + * @param string $cypto as "tls" or "ssl" + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public static function newInstance($host, $port = 110, $crypto = null) + { + return new self($host, $port, $crypto); + } + + /** + * Set a Pop3Connection to delegate to instead of connecting directly. + * + * @param Swift_Plugins_Pop_Pop3Connection $connection + */ + public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection) + { + $this->_connection = $connection; + return $this; + } + + /** + * Bind this plugin to a specific SMTP transport instance. + * + * @param Swift_Transport + */ + public function bindSmtp(Swift_Transport $smtp) + { + $this->_transport = $smtp; + } + + /** + * Set the connection timeout in seconds (default 10). + * + * @param int $timeout + */ + public function setTimeout($timeout) + { + $this->_timeout = (int) $timeout; + return $this; + } + + /** + * Set the username to use when connecting (if needed). + * + * @param string $username + */ + public function setUsername($username) + { + $this->_username = $username; + return $this; + } + + /** + * Set the password to use when connecting (if needed). + * + * @param string $password + */ + public function setPassword($password) + { + $this->_password = $password; + return $this; + } + + /** + * Connect to the POP3 host and authenticate. + * + * @throws Swift_Plugins_Pop_Pop3Exception if connection fails + */ + public function connect() + { + if (isset($this->_connection)) + { + $this->_connection->connect(); + } + else + { + if (!isset($this->_socket)) + { + if (!$socket = fsockopen( + $this->_getHostString(), $this->_port, $errno, $errstr, $this->_timeout)) + { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to connect to POP3 host [%s]: %s', $this->_host, $errstr) + ); + } + $this->_socket = $socket; + + if (false === $greeting = fgets($this->_socket)) + { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to connect to POP3 host [%s]', trim($greeting)) + ); + } + + $this->_assertOk($greeting); + + if ($this->_username) + { + $this->_command(sprintf("USER %s\r\n", $this->_username)); + $this->_command(sprintf("PASS %s\r\n", $this->_password)); + } + } + } + } + + /** + * Disconnect from the POP3 host. + */ + public function disconnect() + { + if (isset($this->_connection)) + { + $this->_connection->disconnect(); + } + else + { + $this->_command("QUIT\r\n"); + if (!fclose($this->_socket)) + { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('POP3 host [%s] connection could not be stopped', $this->_host) + ); + } + $this->_socket = null; + } + } + + /** + * Invoked just before a Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + if (isset($this->_transport)) + { + if ($this->_transport !== $evt->getTransport()) + { + return; + } + } + + $this->connect(); + $this->disconnect(); + } + + /** + * Not used. + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + // -- Private Methods + + private function _command($command) + { + if (!fwrite($this->_socket, $command)) + { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to write command [%s] to POP3 host', trim($command)) + ); + } + + if (false === $response = fgets($this->_socket)) + { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to read from POP3 host after command [%s]', trim($command)) + ); + } + + $this->_assertOk($response); + + return $response; + } + + private function _assertOk($response) + { + if (substr($response, 0, 3) != '+OK') + { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('POP3 command failed [%s]', trim($response)) + ); + } + } + + private function _getHostString() + { + $host = $this->_host; + switch (strtolower($this->_crypto)) + { + case 'ssl': + $host = 'ssl://' . $host; + break; + + case 'tls': + $host = 'tls://' . $host; + break; + } + return $host; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Reporter.php b/lib/Swift/lib/classes/Swift/Plugins/Reporter.php new file mode 100644 index 0000000..00d5765 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Reporter.php @@ -0,0 +1,36 @@ +_reporter = $reporter; + } + + /** + * Not used. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $failures = array_flip($evt->getFailedRecipients()); + foreach ((array) $message->getTo() as $address => $null) + { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + foreach ((array) $message->getCc() as $address => $null) + { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + foreach ((array) $message->getBcc() as $address => $null) + { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Reporters/HitReporter.php b/lib/Swift/lib/classes/Swift/Plugins/Reporters/HitReporter.php new file mode 100644 index 0000000..0022f5e --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Reporters/HitReporter.php @@ -0,0 +1,63 @@ +_failures_cache[$address])) + { + $this->_failures[] = $address; + $this->_failures_cache[$address] = true; + } + } + + /** + * Get an array of addresses for which delivery failed. + * @return array + */ + public function getFailedRecipients() + { + return $this->_failures; + } + + /** + * Clear the buffer (empty the list). + */ + public function clear() + { + $this->_failures = $this->_failures_cache = array(); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php b/lib/Swift/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php new file mode 100644 index 0000000..7370078 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php @@ -0,0 +1,47 @@ +" . PHP_EOL; + echo "PASS " . $address . PHP_EOL; + echo "" . PHP_EOL; + flush(); + } + else + { + echo "
" . PHP_EOL; + echo "FAIL " . $address . PHP_EOL; + echo "
" . PHP_EOL; + flush(); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Sleeper.php b/lib/Swift/lib/classes/Swift/Plugins/Sleeper.php new file mode 100644 index 0000000..148cbd3 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Sleeper.php @@ -0,0 +1,26 @@ +_rate = $rate; + $this->_mode = $mode; + $this->_sleeper = $sleeper; + $this->_timer = $timer; + } + + /** + * Invoked immediately before the Message is sent. + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $time = $this->getTimestamp(); + if (!isset($this->_start)) + { + $this->_start = $time; + } + $duration = $time - $this->_start; + + if (self::BYTES_PER_MINUTE == $this->_mode) + { + $sleep = $this->_throttleBytesPerMinute($duration); + } + else + { + $sleep = $this->_throttleMessagesPerMinute($duration); + } + + if ($sleep > 0) + { + $this->sleep($sleep); + } + } + + /** + * Invoked when a Message is sent. + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + parent::sendPerformed($evt); + ++$this->_messages; + } + + /** + * Sleep for $seconds. + * @param int $seconds + */ + public function sleep($seconds) + { + if (isset($this->_sleeper)) + { + $this->_sleeper->sleep($seconds); + } + else + { + sleep($seconds); + } + } + + /** + * Get the current UNIX timestamp + * @return int + */ + public function getTimestamp() + { + if (isset($this->_timer)) + { + return $this->_timer->getTimestamp(); + } + else + { + return time(); + } + } + + // -- Private methods + + /** + * Get a number of seconds to sleep for. + * @param int $timePassed + * @return int + * @access private + */ + private function _throttleBytesPerMinute($timePassed) + { + $expectedDuration = $this->getBytesOut() / ($this->_rate / 60); + return (int) ceil($expectedDuration - $timePassed); + } + + /** + * Get a number of seconds to sleep for. + * @param int $timePassed + * @return int + * @access private + */ + private function _throttleMessagesPerMinute($timePassed) + { + $expectedDuration = $this->_messages / ($this->_rate / 60); + return (int) ceil($expectedDuration - $timePassed); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Plugins/Timer.php b/lib/Swift/lib/classes/Swift/Plugins/Timer.php new file mode 100644 index 0000000..92207bf --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Plugins/Timer.php @@ -0,0 +1,26 @@ +register('properties.charset')->asValue($charset); + return $this; + } + + /** + * Set the directory where temporary files can be saved. + * @param string $dir + * @return Swift_Preferences + */ + public function setTempDir($dir) + { + Swift_DependencyContainer::getInstance() + ->register('tempdir')->asValue($dir); + return $this; + } + + /** + * Set the type of cache to use (i.e. "disk" or "array"). + * @param string $type + * @return Swift_Preferences + */ + public function setCacheType($type) + { + Swift_DependencyContainer::getInstance() + ->register('cache')->asAliasOf(sprintf('cache.%s', $type)); + return $this; + } + +} diff --git a/lib/Swift/lib/classes/Swift/ReplacementFilterFactory.php b/lib/Swift/lib/classes/Swift/ReplacementFilterFactory.php new file mode 100644 index 0000000..db29e6d --- /dev/null +++ b/lib/Swift/lib/classes/Swift/ReplacementFilterFactory.php @@ -0,0 +1,27 @@ +createDependenciesFor('transport.sendmail') + ); + + $this->setCommand($command); + } + + /** + * Create a new SendmailTransport instance. + * @param string $command + * @return Swift_SendmailTransport + */ + public static function newInstance($command = '/usr/sbin/sendmail -bs') + { + return new self($command); + } + +} diff --git a/lib/Swift/lib/classes/Swift/SmtpTransport.php b/lib/Swift/lib/classes/Swift/SmtpTransport.php new file mode 100644 index 0000000..65180d5 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/SmtpTransport.php @@ -0,0 +1,56 @@ +createDependenciesFor('transport.smtp') + ); + + $this->setHost($host); + $this->setPort($port); + $this->setEncryption($security); + } + + /** + * Create a new SmtpTransport instance. + * @param string $host + * @param int $port + * @param int $security + * @return Swift_SmtpTransport + */ + public static function newInstance($host = 'localhost', $port = 25, + $security = null) + { + return new self($host, $port, $security); + } + +} diff --git a/lib/Swift/lib/classes/Swift/StreamFilter.php b/lib/Swift/lib/classes/Swift/StreamFilter.php new file mode 100644 index 0000000..6c262ce --- /dev/null +++ b/lib/Swift/lib/classes/Swift/StreamFilter.php @@ -0,0 +1,33 @@ +_search = $search; + $this->_index = array(); + $this->_tree = array(); + $this->_replace = array(); + $this->_repSize = array(); + + $tree = null; + $i = null; + $last_size = $size = 0; + foreach ($search as $i => $search_element) + { + if ($tree !== null) + { + $tree[-1] = min (count($replace) - 1, $i - 1); + $tree[-2] = $last_size; + } + $tree = &$this->_tree; + if (is_array ($search_element)) + { + foreach ($search_element as $k => $char) + { + $this->_index[$char] = true; + if (!isset($tree[$char])) + { + $tree[$char] = array(); + } + $tree = &$tree[$char]; + } + $last_size = $k+1; + $size = max($size, $last_size); + } + else + { + $last_size = 1; + if (!isset($tree[$search_element])) + { + $tree[$search_element] = array(); + } + $tree = &$tree[$search_element]; + $size = max($last_size, $size); + $this->_index[$search_element] = true; + } + } + if ($i !== null) + { + $tree[-1] = min (count ($replace) - 1, $i); + $tree[-2] = $last_size; + $this->_treeMaxLen = $size; + } + foreach ($replace as $rep) + { + if (!is_array($rep)) + { + $rep = array ($rep); + } + $this->_replace[] = $rep; + } + for ($i = count($this->_replace) - 1; $i >= 0; --$i) + { + $this->_replace[$i] = $rep = $this->filter($this->_replace[$i], $i); + $this->_repSize[$i] = count($rep); + } + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * @param array $buffer + * @return boolean + */ + public function shouldBuffer($buffer) + { + $endOfBuffer = end($buffer); + return isset ($this->_index[$endOfBuffer]); + } + + /** + * Perform the actual replacements on $buffer and return the result. + * @param array $buffer + * @return array + */ + public function filter($buffer, $_minReplaces = -1) + { + if ($this->_treeMaxLen == 0) + { + return $buffer; + } + + $newBuffer = array(); + $buf_size = count($buffer); + for ($i = 0; $i < $buf_size; ++$i) + { + $search_pos = $this->_tree; + $last_found = PHP_INT_MAX; + // We try to find if the next byte is part of a search pattern + for ($j = 0; $j <= $this->_treeMaxLen; ++$j) + { + // We have a new byte for a search pattern + if (isset ($buffer [$p = $i + $j]) && isset($search_pos[$buffer[$p]])) + { + $search_pos = $search_pos[$buffer[$p]]; + // We have a complete pattern, save, in case we don't find a better match later + if (isset($search_pos[- 1]) && $search_pos[-1] < $last_found + && $search_pos[-1] > $_minReplaces) + { + $last_found = $search_pos[-1]; + $last_size = $search_pos[-2]; + } + } + // We got a complete pattern + elseif ($last_found !== PHP_INT_MAX) + { + // Adding replacement datas to output buffer + $rep_size = $this->_repSize[$last_found]; + for ($j = 0; $j < $rep_size; ++$j) + { + $newBuffer[] = $this->_replace[$last_found][$j]; + } + // We Move cursor forward + $i += $last_size - 1; + // Edge Case, last position in buffer + if ($i >= $buf_size) + { + $newBuffer[] = $buffer[$i]; + } + + // We start the next loop + continue 2; + } + else + { + // this byte is not in a pattern and we haven't found another pattern + break; + } + } + // Normal byte, move it to output buffer + $newBuffer[] = $buffer[$i]; + } + + return $newBuffer; + } + +} diff --git a/lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilter.php b/lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilter.php new file mode 100644 index 0000000..9ab6c30 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilter.php @@ -0,0 +1,66 @@ +_search = $search; + $this->_replace = $replace; + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * @param string $buffer + * @return boolean + */ + public function shouldBuffer($buffer) + { + $endOfBuffer = substr($buffer, -1); + foreach ((array) $this->_search as $needle) + { + if (false !== strpos($needle, $endOfBuffer)) + { + return true; + } + } + return false; + } + + /** + * Perform the actual replacements on $buffer and return the result. + * @param string $buffer + * @return string + */ + public function filter($buffer) + { + return str_replace($this->_search, $this->_replace, $buffer); + } + +} diff --git a/lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php b/lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php new file mode 100644 index 0000000..fcd4b83 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php @@ -0,0 +1,53 @@ +_filters[$search][$replace])) + { + if (!isset($this->_filters[$search])) + { + $this->_filters[$search] = array(); + } + + if (!isset($this->_filters[$search][$replace])) + { + $this->_filters[$search][$replace] = array(); + } + + $this->_filters[$search][$replace] + = new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + return $this->_filters[$search][$replace]; + } + +} diff --git a/lib/Swift/lib/classes/Swift/SwiftException.php b/lib/Swift/lib/classes/Swift/SwiftException.php new file mode 100644 index 0000000..bd3b656 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/SwiftException.php @@ -0,0 +1,28 @@ +_eventDispatcher = $dispatcher; + $this->_buffer = $buf; + $this->_lookupHostname(); + } + + /** + * Set the name of the local domain which Swift will identify itself as. + * This should be a fully-qualified domain name and should be truly the domain + * you're using. If your server doesn't have a domain name, use the IP in square + * brackets (i.e. [127.0.0.1]). + * + * @param string $domain + */ + public function setLocalDomain($domain) + { + $this->_domain = $domain; + return $this; + } + + /** + * Get the name of the domain Swift will identify as. + * + * @return string + */ + public function getLocalDomain() + { + return $this->_domain; + } + + /** + * Start the SMTP connection. + */ + public function start() + { + if (!$this->_started) + { + if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted'); + if ($evt->bubbleCancelled()) + { + return; + } + } + + try + { + $this->_buffer->initialize($this->_getBufferParams()); + } + catch (Swift_TransportException $e) + { + $this->_throwException($e); + } + $this->_readGreeting(); + $this->_doHeloCommand(); + + if ($evt) + { + $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted'); + } + + $this->_started = true; + } + } + + /** + * Test if an SMTP connection has been established. + * + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retreived from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] &$failedRecipients to collect failures by-reference + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $sent = 0; + $failedRecipients = (array) $failedRecipients; + + if (!$reversePath = $this->_getReversePath($message)) + { + throw new Swift_TransportException( + 'Cannot send message without a sender address' + ); + } + + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) + { + return 0; + } + } + + $to = (array) $message->getTo(); + $cc = (array) $message->getCc(); + $bcc = (array) $message->getBcc(); + + $message->setBcc(array()); + + try + { + $sent += $this->_sendTo($message, $reversePath, $to, $failedRecipients); + $sent += $this->_sendCc($message, $reversePath, $cc, $failedRecipients); + $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients); + } + catch (Exception $e) + { + $message->setBcc($bcc); + throw $e; + } + + $message->setBcc($bcc); + + if ($evt) + { + if ($sent == count($to) + count($cc) + count($bcc)) + { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + } + elseif ($sent > 0) + { + $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE); + } + else + { + $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); + } + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); //Make sure a new Message ID is used + + return $sent; + } + + /** + * Stop the SMTP connection. + */ + public function stop() + { + if ($this->_started) + { + if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); + if ($evt->bubbleCancelled()) + { + return; + } + } + + try + { + $this->executeCommand("QUIT\r\n", array(221)); + } + catch (Swift_TransportException $e) {} + + try + { + $this->_buffer->terminate(); + + if ($evt) + { + $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped'); + } + } + catch (Swift_TransportException $e) + { + $this->_throwException($e); + } + } + $this->_started = false; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } + + /** + * Reset the current mail transaction. + */ + public function reset() + { + $this->executeCommand("RSET\r\n", array(250)); + } + + /** + * Get the IoBuffer where read/writes are occurring. + * + * @return Swift_Transport_IoBuffer + */ + public function getBuffer() + { + return $this->_buffer; + } + + /** + * Run a command against the buffer, expecting the given response codes. + * + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * + * @param string $command + * @param int[] $codes + * @param string[] &$failures + * @return string + */ + public function executeCommand($command, $codes = array(), &$failures = null) + { + $failures = (array) $failures; + $seq = $this->_buffer->write($command); + $response = $this->_getFullResponse($seq); + if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'commandSent'); + } + $this->_assertResponseCode($response, $codes); + return $response; + } + + // -- Protected methods + + /** Read the opening SMTP greeting */ + protected function _readGreeting() + { + $this->_assertResponseCode($this->_getFullResponse(0), array(220)); + } + + /** Send the HELO welcome */ + protected function _doHeloCommand() + { + $this->executeCommand( + sprintf("HELO %s\r\n", $this->_domain), array(250) + ); + } + + /** Send the MAIL FROM command */ + protected function _doMailFromCommand($address) + { + $this->executeCommand( + sprintf("MAIL FROM: <%s>\r\n", $address), array(250) + ); + } + + /** Send the RCPT TO command */ + protected function _doRcptToCommand($address) + { + $this->executeCommand( + sprintf("RCPT TO: <%s>\r\n", $address), array(250, 251, 252) + ); + } + + /** Send the DATA command */ + protected function _doDataCommand() + { + $this->executeCommand("DATA\r\n", array(354)); + } + + /** Stream the contents of the message over the buffer */ + protected function _streamMessage(Swift_Mime_Message $message) + { + $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n..")); + try + { + $message->toByteStream($this->_buffer); + $this->_buffer->flushBuffers(); + } + catch (Swift_TransportException $e) + { + $this->_throwException($e); + } + $this->_buffer->setWriteTranslations(array()); + $this->executeCommand("\r\n.\r\n", array(250)); + } + + /** Determine the best-use reverse path for this message */ + protected function _getReversePath(Swift_Mime_Message $message) + { + $return = $message->getReturnPath(); + $sender = $message->getSender(); + $from = $message->getFrom(); + $path = null; + if (!empty($return)) + { + $path = $return; + } + elseif (!empty($sender)) + { + // Don't use array_keys + reset($sender); // Reset Pointer to first pos + $path = key($sender); // Get key + } + elseif (!empty($from)) + { + reset($from); // Reset Pointer to first pos + $path = key($from); // Get key + } + return $path; + } + + /** Throw a TransportException, first sending it to any listeners */ + protected function _throwException(Swift_TransportException $e) + { + if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown'); + if (!$evt->bubbleCancelled()) + { + throw $e; + } + } + else + { + throw $e; + } + } + + /** Throws an Exception if a response code is incorrect */ + protected function _assertResponseCode($response, $wanted) + { + list($code, $separator, $text) = sscanf($response, '%3d%[ -]%s'); + $valid = (empty($wanted) || in_array($code, $wanted)); + + if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response, + $valid)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived'); + } + + if (!$valid) + { + $this->_throwException( + new Swift_TransportException( + 'Expected response code ' . implode('/', $wanted) . ' but got code ' . + '"' . $code . '", with message "' . $response . '"' + ) + ); + } + } + + /** Get an entire multi-line response using its sequence number */ + protected function _getFullResponse($seq) + { + $response = ''; + try + { + do + { + $line = $this->_buffer->readLine($seq); + $response .= $line; + } + while (null !== $line && false !== $line && ' ' != $line{3}); + } + catch (Swift_TransportException $e) + { + $this->_throwException($e); + } + return $response; + } + + // -- Private methods + + /** Send an email to the given recipients from the given reverse path */ + private function _doMailTransaction($message, $reversePath, + array $recipients, array &$failedRecipients) + { + $sent = 0; + $this->_doMailFromCommand($reversePath); + foreach ($recipients as $forwardPath) + { + try + { + $this->_doRcptToCommand($forwardPath); + $sent++; + } + catch (Swift_TransportException $e) + { + $failedRecipients[] = $forwardPath; + } + } + + if ($sent != 0) + { + $this->_doDataCommand(); + $this->_streamMessage($message); + } + else + { + $this->reset(); + } + + return $sent; + } + + /** Send a message to the given To: recipients */ + private function _sendTo(Swift_Mime_Message $message, $reversePath, + array $to, array &$failedRecipients) + { + if (empty($to)) + { + return 0; + } + return $this->_doMailTransaction($message, $reversePath, array_keys($to), + $failedRecipients); + } + + /** Send a message to the given Cc: recipients */ + private function _sendCc(Swift_Mime_Message $message, $reversePath, + array $cc, array &$failedRecipients) + { + if (empty($cc)) + { + return 0; + } + return $this->_doMailTransaction($message, $reversePath, array_keys($cc), + $failedRecipients); + } + + /** Send a message to all Bcc: recipients */ + private function _sendBcc(Swift_Mime_Message $message, $reversePath, + array $bcc, array &$failedRecipients) + { + $sent = 0; + foreach ($bcc as $forwardPath => $name) + { + $message->setBcc(array($forwardPath => $name)); + $sent += $this->_doMailTransaction( + $message, $reversePath, array($forwardPath), $failedRecipients + ); + } + return $sent; + } + + /** Try to determine the hostname of the server this is run on */ + private function _lookupHostname() + { + if (!empty($_SERVER['SERVER_NAME']) + && $this->_isFqdn($_SERVER['SERVER_NAME'])) + { + $this->_domain = $_SERVER['SERVER_NAME']; + } + elseif (!empty($_SERVER['SERVER_ADDR'])) + { + $this->_domain = sprintf('[%s]', $_SERVER['SERVER_ADDR']); + } + } + + /** Determine is the $hostname is a fully-qualified name */ + private function _isFqdn($hostname) + { + //We could do a really thorough check, but there's really no point + if (false !== $dotPos = strpos($hostname, '.')) + { + return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1); + } + else + { + return false; + } + } + + /** + * Destructor. + */ + public function __destruct() + { + $this->stop(); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php new file mode 100644 index 0000000..4c7e0f2 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php @@ -0,0 +1,88 @@ +executeCommand("AUTH CRAM-MD5\r\n", array(334)); + $challenge = base64_decode(substr($challenge, 4)); + $message = base64_encode( + $username . ' ' . $this->_getResponse($password, $challenge) + ); + $agent->executeCommand(sprintf("%s\r\n", $message), array(235)); + return true; + } + catch (Swift_TransportException $e) + { + $agent->executeCommand("RSET\r\n", array(250)); + return false; + } + } + + /** + * Generate a CRAM-MD5 response from a server challenge. + * @param string $secret + * @param string $challenge + * @return string + */ + private function _getResponse($secret, $challenge) + { + if (strlen($secret) > 64) + { + $secret = pack('H32', md5($secret)); + } + + if (strlen($secret) < 64) + { + $secret = str_pad($secret, 64, chr(0)); + } + + $k_ipad = substr($secret, 0, 64) ^ str_repeat(chr(0x36), 64); + $k_opad = substr($secret, 0, 64) ^ str_repeat(chr(0x5C), 64); + + $inner = pack('H32', md5($k_ipad . $challenge)); + $digest = md5($k_opad . $inner); + + return $digest; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php new file mode 100644 index 0000000..bd22617 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php @@ -0,0 +1,58 @@ +executeCommand("AUTH LOGIN\r\n", array(334)); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($username)), array(334)); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($password)), array(235)); + return true; + } + catch (Swift_TransportException $e) + { + $agent->executeCommand("RSET\r\n", array(250)); + return false; + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php new file mode 100644 index 0000000..ddd8094 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php @@ -0,0 +1,57 @@ +executeCommand(sprintf("AUTH PLAIN %s\r\n", $message), array(235)); + return true; + } + catch (Swift_TransportException $e) + { + $agent->executeCommand("RSET\r\n", array(250)); + return false; + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/Esmtp/AuthHandler.php b/lib/Swift/lib/classes/Swift/Transport/Esmtp/AuthHandler.php new file mode 100644 index 0000000..a223169 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/Esmtp/AuthHandler.php @@ -0,0 +1,262 @@ +setAuthenticators($authenticators); + } + + /** + * Set the Authenticators which can process a login request. + * @param Swift_Transport_Esmtp_Authenticator[] $authenticators + */ + public function setAuthenticators(array $authenticators) + { + $this->_authenticators = $authenticators; + } + + /** + * Get the Authenticators which can process a login request. + * @return Swift_Transport_Esmtp_Authenticator[] + */ + public function getAuthenticators() + { + return $this->_authenticators; + } + + /** + * Set the username to authenticate with. + * @param string $username + */ + public function setUsername($username) + { + $this->_username = $username; + } + + /** + * Get the username to authenticate with. + * @return string + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Set the password to authenticate with. + * @param string $password + */ + public function setPassword($password) + { + $this->_password = $password; + } + + /** + * Get the password to authenticate with. + * @return string + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Set the auth mode to use to authenticate. + * @param string $mode + */ + public function setAuthMode($mode) + { + $this->_auth_mode = $mode; + } + + /** + * Get the auth mode to use to authenticate. + * @return string + */ + public function getAuthMode() + { + return $this->_auth_mode; + } + + /** + * Get the name of the ESMTP extension this handles. + * @return boolean + */ + public function getHandledKeyword() + { + return 'AUTH'; + } + + /** + * Set the parameters which the EHLO greeting indicated. + * @param string[] $parameters + */ + public function setKeywordParams(array $parameters) + { + $this->_esmtpParams = $parameters; + } + + /** + * Runs immediately after a EHLO has been issued. + * @param Swift_Transport_SmtpAgent $agent to read/write + */ + public function afterEhlo(Swift_Transport_SmtpAgent $agent) + { + if ($this->_username) + { + $count = 0; + foreach ($this->_getAuthenticatorsForAgent() as $authenticator) + { + if (in_array(strtolower($authenticator->getAuthKeyword()), + array_map('strtolower', $this->_esmtpParams))) + { + $count++; + if ($authenticator->authenticate($agent, $this->_username, $this->_password)) + { + return; + } + } + } + throw new Swift_TransportException( + 'Failed to authenticate on SMTP server with username "' . + $this->_username . '" using ' . $count . ' possible authenticators' + ); + } + } + + /** + * Not used. + */ + public function getMailParams() + { + return array(); + } + + /** + * Not used. + */ + public function getRcptParams() + { + return array(); + } + + /** + * Not used. + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, + $command, $codes = array(), &$failedRecipients = null, &$stop = false) + { + } + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * This method is called to ensure extensions can be execute in an appropriate order. + * @param string $esmtpKeyword to compare with + * @return int + */ + public function getPriorityOver($esmtpKeyword) + { + return 0; + } + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * @return string[] + */ + public function exposeMixinMethods() + { + return array('setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode'); + } + + /** + * Not used. + */ + public function resetState() + { + } + + // -- Protected methods + + /** + * Returns the authenticator list for the given agent. + * @param Swift_Transport_SmtpAgent $agent + * @return array + * @access protected + */ + protected function _getAuthenticatorsForAgent() + { + if (!$mode = strtolower($this->_auth_mode)) + { + return $this->_authenticators; + } + + foreach ($this->_authenticators as $authenticator) + { + if (strtolower($authenticator->getAuthKeyword()) == $mode) + { + return array($authenticator); + } + } + + throw new Swift_TransportException('Auth mode '.$mode.' is invalid'); + } +} diff --git a/lib/Swift/lib/classes/Swift/Transport/Esmtp/Authenticator.php b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Authenticator.php new file mode 100644 index 0000000..bf166d3 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/Esmtp/Authenticator.php @@ -0,0 +1,38 @@ +. + * @return string[] + */ + public function getMailParams(); + + /** + * Get params which are appended to RCPT TO:<>. + * @return string[] + */ + public function getRcptParams(); + + /** + * Runs when a command is due to be sent. + * @param Swift_Transport_SmtpAgent $agent to read/write + * @param string $command to send + * @param int[] $codes expected in response + * @param string[] &$failedRecipients + * @param boolean &$stop to be set true if the command is now sent + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, + $command, $codes = array(), &$failedRecipients = null, &$stop = false); + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * This method is called to ensure extensions can be execute in an appropriate order. + * @param string $esmtpKeyword to compare with + * @return int + */ + public function getPriorityOver($esmtpKeyword); + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * @return string[] + */ + public function exposeMixinMethods(); + + /** + * Tells this handler to clear any buffers and reset its state. + */ + public function resetState(); + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/EsmtpTransport.php b/lib/Swift/lib/classes/Swift/Transport/EsmtpTransport.php new file mode 100644 index 0000000..c7833c3 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/EsmtpTransport.php @@ -0,0 +1,340 @@ + 'tcp', + 'host' => 'localhost', + 'port' => 25, + 'timeout' => 30, + 'blocking' => 1, + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET + ); + + /** + * Creates a new EsmtpTransport using the given I/O buffer. + * @param Swift_Transport_IoBuffer $buf + * @param Swift_Transport_EsmtpHandler[] $extensionHandlers + * @param Swift_Events_EventDispatcher $dispatcher + */ + public function __construct(Swift_Transport_IoBuffer $buf, + array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher) + { + parent::__construct($buf, $dispatcher); + $this->setExtensionHandlers($extensionHandlers); + } + + /** + * Set the host to connect to. + * @param string $host + */ + public function setHost($host) + { + $this->_params['host'] = $host; + return $this; + } + + /** + * Get the host to connect to. + * @return string + */ + public function getHost() + { + return $this->_params['host']; + } + + /** + * Set the port to connect to. + * @param int $port + */ + public function setPort($port) + { + $this->_params['port'] = (int) $port; + return $this; + } + + /** + * Get the port to connect to. + * @return int + */ + public function getPort() + { + return $this->_params['port']; + } + + /** + * Set the connection timeout. + * @param int $timeout seconds + */ + public function setTimeout($timeout) + { + $this->_params['timeout'] = (int) $timeout; + return $this; + } + + /** + * Get the connection timeout. + * @return int + */ + public function getTimeout() + { + return $this->_params['timeout']; + } + + /** + * Set the encryption type (tls or ssl) + * @param string $encryption + */ + public function setEncryption($enc) + { + $this->_params['protocol'] = $enc; + return $this; + } + + /** + * Get the encryption type. + * @return string + */ + public function getEncryption() + { + return $this->_params['protocol']; + } + + /** + * Set ESMTP extension handlers. + * @param Swift_Transport_EsmtpHandler[] $handlers + */ + public function setExtensionHandlers(array $handlers) + { + $assoc = array(); + foreach ($handlers as $handler) + { + $assoc[$handler->getHandledKeyword()] = $handler; + } + uasort($assoc, array($this, '_sortHandlers')); + $this->_handlers = $assoc; + $this->_setHandlerParams(); + return $this; + } + + /** + * Get ESMTP extension handlers. + * @return Swift_Transport_EsmtpHandler[] + */ + public function getExtensionHandlers() + { + return array_values($this->_handlers); + } + + /** + * Run a command against the buffer, expecting the given response codes. + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * @param string $command + * @param int[] $codes + * @param string[] &$failures + * @return string + */ + public function executeCommand($command, $codes = array(), &$failures = null) + { + $failures = (array) $failures; + $stopSignal = false; + $response = null; + foreach ($this->_getActiveHandlers() as $handler) + { + $response = $handler->onCommand( + $this, $command, $codes, $failures, $stopSignal + ); + if ($stopSignal) + { + return $response; + } + } + return parent::executeCommand($command, $codes, $failures); + } + + // -- Mixin invocation code + + /** Mixin handling method for ESMTP handlers */ + public function __call($method, $args) + { + foreach ($this->_handlers as $handler) + { + if (in_array(strtolower($method), + array_map('strtolower', (array) $handler->exposeMixinMethods()) + )) + { + $return = call_user_func_array(array($handler, $method), $args); + //Allow fluid method calls + if (is_null($return) && substr($method, 0, 3) == 'set') + { + return $this; + } + else + { + return $return; + } + } + } + trigger_error('Call to undefined method ' . $method, E_USER_ERROR); + } + + // -- Protected methods + + /** Get the params to initialize the buffer */ + protected function _getBufferParams() + { + return $this->_params; + } + + /** Overridden to perform EHLO instead */ + protected function _doHeloCommand() + { + try + { + $response = $this->executeCommand( + sprintf("EHLO %s\r\n", $this->_domain), array(250) + ); + } + catch (Swift_TransportException $e) + { + return parent::_doHeloCommand(); + } + + $this->_capabilities = $this->_getCapabilities($response); + $this->_setHandlerParams(); + foreach ($this->_getActiveHandlers() as $handler) + { + $handler->afterEhlo($this); + } + } + + /** Overridden to add Extension support */ + protected function _doMailFromCommand($address) + { + $handlers = $this->_getActiveHandlers(); + $params = array(); + foreach ($handlers as $handler) + { + $params = array_merge($params, (array) $handler->getMailParams()); + } + $paramStr = !empty($params) ? ' ' . implode(' ', $params) : ''; + $this->executeCommand( + sprintf("MAIL FROM: <%s>%s\r\n", $address, $paramStr), array(250) + ); + } + + /** Overridden to add Extension support */ + protected function _doRcptToCommand($address) + { + $handlers = $this->_getActiveHandlers(); + $params = array(); + foreach ($handlers as $handler) + { + $params = array_merge($params, (array) $handler->getRcptParams()); + } + $paramStr = !empty($params) ? ' ' . implode(' ', $params) : ''; + $this->executeCommand( + sprintf("RCPT TO: <%s>%s\r\n", $address, $paramStr), array(250, 251, 252) + ); + } + + // -- Private methods + + /** Determine ESMTP capabilities by function group */ + private function _getCapabilities($ehloResponse) + { + $capabilities = array(); + $ehloResponse = trim($ehloResponse); + $lines = explode("\r\n", $ehloResponse); + array_shift($lines); + foreach ($lines as $line) + { + if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) + { + $keyword = strtoupper($matches[1]); + $paramStr = strtoupper(ltrim($matches[2], ' =')); + $params = !empty($paramStr) ? explode(' ', $paramStr) : array(); + $capabilities[$keyword] = $params; + } + } + return $capabilities; + } + + /** Set parameters which are used by each extension handler */ + private function _setHandlerParams() + { + foreach ($this->_handlers as $keyword => $handler) + { + if (array_key_exists($keyword, $this->_capabilities)) + { + $handler->setKeywordParams($this->_capabilities[$keyword]); + } + } + } + + /** Get ESMTP handlers which are currently ok to use */ + private function _getActiveHandlers() + { + $handlers = array(); + foreach ($this->_handlers as $keyword => $handler) + { + if (array_key_exists($keyword, $this->_capabilities)) + { + $handlers[] = $handler; + } + } + return $handlers; + } + + /** Custom sort for extension handler ordering */ + private function _sortHandlers($a, $b) + { + return $a->getPriorityOver($b->getHandledKeyword()); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/FailoverTransport.php b/lib/Swift/lib/classes/Swift/Transport/FailoverTransport.php new file mode 100644 index 0000000..e62491c --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/FailoverTransport.php @@ -0,0 +1,97 @@ +_transports); + $sent = 0; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) + { + try + { + if (!$transport->isStarted()) + { + $transport->start(); + } + + return $transport->send($message, $failedRecipients); + } + catch (Swift_TransportException $e) + { + $this->_killCurrentTransport(); + } + } + + if (count($this->_transports) == 0) + { + throw new Swift_TransportException( + 'All Transports in FailoverTransport failed, or no Transports available' + ); + } + + return $sent; + } + + // -- Protected methods + + protected function _getNextTransport() + { + if (!isset($this->_currentTransport)) + { + $this->_currentTransport = parent::_getNextTransport(); + } + return $this->_currentTransport; + } + + protected function _killCurrentTransport() + { + $this->_currentTransport = null; + parent::_killCurrentTransport(); + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/IoBuffer.php b/lib/Swift/lib/classes/Swift/Transport/IoBuffer.php new file mode 100644 index 0000000..ac66ef0 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/IoBuffer.php @@ -0,0 +1,65 @@ +_transports = $transports; + $this->_deadTransports = array(); + } + + /** + * Get $transports to delegate to. + * + * @return array Swift_Transport + */ + public function getTransports(array $transports) + { + return array_merge($this->_transports, $this->_deadTransports); + } + + /** + * Test if this Transport mechanism has started. + * + * @return boolean + */ + public function isStarted() + { + return count($this->_transports) > 0; + } + + /** + * Start this Transport mechanism. + */ + public function start() + { + $this->_transports = array_merge($this->_transports, $this->_deadTransports); + } + + /** + * Stop this Transport mechanism. + */ + public function stop() + { + foreach ($this->_transports as $transport) + { + $transport->stop(); + } + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retreived from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] &$failedRecipients to collect failures by-reference + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $maxTransports = count($this->_transports); + $sent = 0; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) + { + try + { + if (!$transport->isStarted()) + { + $transport->start(); + } + if ($sent = $transport->send($message, $failedRecipients)) + { + break; + } + } + catch (Swift_TransportException $e) + { + $this->_killCurrentTransport(); + } + } + + if (count($this->_transports) == 0) + { + throw new Swift_TransportException( + 'All Transports in LoadBalancedTransport failed, or no Transports available' + ); + } + + return $sent; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + foreach ($this->_transports as $transport) + { + $transport->registerPlugin($plugin); + } + } + + // -- Protected methods + + /** + * Rotates the transport list around and returns the first instance. + * + * @return Swift_Transport + * @access protected + */ + protected function _getNextTransport() + { + if ($next = array_shift($this->_transports)) + { + $this->_transports[] = $next; + } + return $next; + } + + /** + * Tag the currently used (top of stack) transport as dead/useless. + * + * @access protected + */ + protected function _killCurrentTransport() + { + if ($transport = array_pop($this->_transports)) + { + try + { + $transport->stop(); + } + catch (Exception $e) + { + } + $this->_deadTransports[] = $transport; + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/MailInvoker.php b/lib/Swift/lib/classes/Swift/Transport/MailInvoker.php new file mode 100644 index 0000000..dda882f --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/MailInvoker.php @@ -0,0 +1,36 @@ +_invoker = $invoker; + $this->_eventDispatcher = $eventDispatcher; + } + + /** + * Not used. + */ + public function isStarted() + { + return false; + } + + /** + * Not used. + */ + public function start() + { + } + + /** + * Not used. + */ + public function stop() + { + } + + /** + * Set the additional parameters used on the mail() function. + * + * This string is formatted for sprintf() where %s is the sender address. + * + * @param string $params + */ + public function setExtraParams($params) + { + $this->_extraParams = $params; + return $this; + } + + /** + * Get the additional parameters used on the mail() function. + * + * This string is formatted for sprintf() where %s is the sender address. + * + * @return string + */ + public function getExtraParams() + { + return $this->_extraParams; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retreived from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] &$failedRecipients to collect failures by-reference + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) + { + return 0; + } + } + + $count = ( + count((array) $message->getTo()) + + count((array) $message->getCc()) + + count((array) $message->getBcc()) + ); + + $toHeader = $message->getHeaders()->get('To'); + $subjectHeader = $message->getHeaders()->get('Subject'); + + $to = $toHeader->getFieldBody(); + $subject = $subjectHeader->getFieldBody(); + + $reversePath = $this->_getReversePath($message); + + //Remove headers that would otherwise be duplicated + $message->getHeaders()->remove('To'); + $message->getHeaders()->remove('Subject'); + + $messageStr = $message->toString(); + + $message->getHeaders()->set($toHeader); + $message->getHeaders()->set($subjectHeader); + + //Separate headers from body + if (false !== $endHeaders = strpos($messageStr, "\r\n\r\n")) + { + $headers = substr($messageStr, 0, $endHeaders) . "\r\n"; //Keep last EOL + $body = substr($messageStr, $endHeaders + 4); + } + else + { + $headers = $messageStr . "\r\n"; + $body = ''; + } + + unset($messageStr); + + if ("\r\n" != PHP_EOL) //Non-windows (not using SMTP) + { + $headers = str_replace("\r\n", PHP_EOL, $headers); + $body = str_replace("\r\n", PHP_EOL, $body); + } + else //Windows, using SMTP + { + $headers = str_replace("\r\n.", "\r\n..", $headers); + $body = str_replace("\r\n.", "\r\n..", $body); + } + + if ($this->_invoker->mail($to, $subject, $body, $headers, + sprintf($this->_extraParams, $reversePath))) + { + if ($evt) + { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + } + else + { + $failedRecipients = array_merge( + $failedRecipients, + array_keys((array) $message->getTo()), + array_keys((array) $message->getCc()), + array_keys((array) $message->getBcc()) + ); + + if ($evt) + { + $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); + + $count = 0; + } + + return $count; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } + + // -- Private methods + + /** Determine the best-use reverse path for this message */ + private function _getReversePath(Swift_Mime_Message $message) + { + $return = $message->getReturnPath(); + $sender = $message->getSender(); + $from = $message->getFrom(); + $path = null; + if (!empty($return)) + { + $path = $return; + } + elseif (!empty($sender)) + { + $keys = array_keys($sender); + $path = array_shift($keys); + } + elseif (!empty($from)) + { + $keys = array_keys($from); + $path = array_shift($keys); + } + return $path; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/SendmailTransport.php b/lib/Swift/lib/classes/Swift/Transport/SendmailTransport.php new file mode 100644 index 0000000..aae8bde --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/SendmailTransport.php @@ -0,0 +1,173 @@ + 30, + 'blocking' => 1, + 'command' => '/usr/sbin/sendmail -bs', + 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS + ); + + /** + * Create a new SendmailTransport with $buf for I/O. + * @param Swift_Transport_IoBuffer $buf + * @param Swift_Events_EventDispatcher $dispatcher + */ + public function __construct(Swift_Transport_IoBuffer $buf, + Swift_Events_EventDispatcher $dispatcher) + { + parent::__construct($buf, $dispatcher); + } + + /** + * Start the standalone SMTP session if running in -bs mode. + */ + public function start() + { + if (false !== strpos($this->getCommand(), ' -bs')) + { + parent::start(); + } + } + + /** + * Set the command to invoke. + * If using -t mode you are strongly advised to include -oi or -i in the + * flags. For example: /usr/sbin/sendmail -oi -t + * Swift will append a -f flag if one is not present. + * The recommended mode is "-bs" since it is interactive and failure notifications + * are hence possible. + * @param string $command + */ + public function setCommand($command) + { + $this->_params['command'] = $command; + return $this; + } + + /** + * Get the sendmail command which will be invoked. + * @return string + */ + public function getCommand() + { + return $this->_params['command']; + } + + /** + * Send the given Message. + * Recipient/sender data will be retreived from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * NOTE: If using 'sendmail -t' you will not be aware of any failures until + * they bounce (i.e. send() will always return 100% success). + * @param Swift_Mime_Message $message + * @param string[] &$failedRecipients to collect failures by-reference + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + $command = $this->getCommand(); + $buffer = $this->getBuffer(); + + if (false !== strpos($command, ' -t')) + { + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) + { + return 0; + } + } + + if (false === strpos($command, ' -f')) + { + $command .= ' -f' . $this->_getReversePath($message); + } + + $buffer->initialize(array_merge($this->_params, array('command' => $command))); + + if (false === strpos($command, ' -i') && false === strpos($command, ' -oi')) + { + $buffer->setWriteTranslations(array("\r\n" => "\n", "\n." => "\n..")); + } + else + { + $buffer->setWriteTranslations(array("\r\n"=>"\n")); + } + + $count = count((array) $message->getTo()) + + count((array) $message->getCc()) + + count((array) $message->getBcc()) + ; + $message->toByteStream($buffer); + $buffer->flushBuffers(); + $buffer->setWriteTranslations(array()); + $buffer->terminate(); + + if ($evt) + { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); + } + elseif (false !== strpos($command, ' -bs')) + { + $count = parent::send($message, $failedRecipients); + } + else + { + $this->_throwException(new Swift_TransportException( + 'Unsupported sendmail command flags [' . $command . ']. ' . + 'Must be one of "-bs" or "-t" but can include additional flags.' + )); + } + + return $count; + } + + // -- Protected methods + + /** Get the params to initialize the buffer */ + protected function _getBufferParams() + { + return $this->_params; + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/SimpleMailInvoker.php b/lib/Swift/lib/classes/Swift/Transport/SimpleMailInvoker.php new file mode 100644 index 0000000..271ba84 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/SimpleMailInvoker.php @@ -0,0 +1,58 @@ +. + + */ + +//@require 'Swift/Transport/MailInvoker.php'; + +/** + * This is the implementation class for {@link Swift_Transport_MailInvoker}. + * + * @package Swift + * @subpackage Transport + * @author Chris Corbyn + */ +class Swift_Transport_SimpleMailInvoker implements Swift_Transport_MailInvoker +{ + + /** + * Send mail via the mail() function. + * + * This method takes the same arguments as PHP mail(). + * + * @param string $to + * @param string $subject + * @param string $body + * @param string $headers + * @param string $extraParams + * + * @return boolean + */ + public function mail($to, $subject, $body, $headers = null, $extraParams = null) + { + if (!ini_get('safe_mode')) + { + return mail($to, $subject, $body, $headers, $extraParams); + } + else + { + return mail($to, $subject, $body, $headers); + } + } + +} diff --git a/lib/Swift/lib/classes/Swift/Transport/SmtpAgent.php b/lib/Swift/lib/classes/Swift/Transport/SmtpAgent.php new file mode 100644 index 0000000..ee9b8ed --- /dev/null +++ b/lib/Swift/lib/classes/Swift/Transport/SmtpAgent.php @@ -0,0 +1,36 @@ +_replacementFactory = $replacementFactory; + } + + /** + * Perform any initialization needed, using the given $params. + * Parameters will vary depending upon the type of IoBuffer used. + * @param array $params + */ + public function initialize(array $params) + { + $this->_params = $params; + switch ($params['type']) + { + case self::TYPE_PROCESS: + $this->_establishProcessConnection(); + break; + case self::TYPE_SOCKET: + default: + $this->_establishSocketConnection(); + break; + } + } + + /** + * Set an individual param on the buffer (e.g. switching to SSL). + * @param string $param + * @param mixed $value + */ + public function setParam($param, $value) + { + if (isset($this->_stream)) + { + switch ($param) + { + case 'protocol': + if (!array_key_exists('protocol', $this->_params) + || $value != $this->_params['protocol']) + { + if ('tls' == $value) + { + stream_socket_enable_crypto( + $this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT + ); + } + } + break; + } + } + $this->_params[$param] = $value; + } + + /** + * Perform any shutdown logic needed. + */ + public function terminate() + { + if (isset($this->_stream)) + { + switch ($this->_params['type']) + { + case self::TYPE_PROCESS: + fclose($this->_in); + fclose($this->_out); + proc_close($this->_stream); + break; + case self::TYPE_SOCKET: + default: + fclose($this->_stream); + break; + } + } + $this->_stream = null; + $this->_out = null; + $this->_in = null; + } + + /** + * Set an array of string replacements which should be made on data written + * to the buffer. This could replace LF with CRLF for example. + * @param string[] $replacements + */ + public function setWriteTranslations(array $replacements) + { + foreach ($this->_translations as $search => $replace) + { + if (!isset($replacements[$search])) + { + $this->removeFilter($search); + unset($this->_translations[$search]); + } + } + + foreach ($replacements as $search => $replace) + { + if (!isset($this->_translations[$search])) + { + $this->addFilter( + $this->_replacementFactory->createFilter($search, $replace), $search + ); + $this->_translations[$search] = true; + } + } + } + + /** + * Get a line of output (including any CRLF). + * The $sequence number comes from any writes and may or may not be used + * depending upon the implementation. + * @param int $sequence of last write to scan from + * @return string + */ + public function readLine($sequence) + { + if (isset($this->_out) && !feof($this->_out)) + { + $line = fgets($this->_out); + return $line; + } + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. If less bytes exist than are requested the + * remaining bytes are given instead. If no bytes are remaining at all, boolean + * false is returned. + * @param int $length + * @return string + */ + public function read($length) + { + if (isset($this->_out) && !feof($this->_out)) + { + $ret = fread($this->_out, $length); + return $ret; + } + } + + /** Not implemented */ + public function setReadPointer($byteOffset) + { + } + + // -- Protected methods + + /** Flush the stream contents */ + protected function _flush() + { + if (isset($this->_in)) + { + fflush($this->_in); + } + } + + /** Write this bytes to the stream */ + protected function _commit($bytes) + { + if (isset($this->_in) + && fwrite($this->_in, $bytes)) + { + return ++$this->_sequence; + } + } + + // -- Private methods + + /** + * Establishes a connection to a remote server. + * @access private + */ + private function _establishSocketConnection() + { + $host = $this->_params['host']; + if (!empty($this->_params['protocol'])) + { + $host = $this->_params['protocol'] . '://' . $host; + } + $timeout = 15; + if (!empty($this->_params['timeout'])) + { + $timeout = $this->_params['timeout']; + } + if (!$this->_stream = fsockopen($host, $this->_params['port'], $errno, $errstr, $timeout)) + { + throw new Swift_TransportException( + 'Connection could not be established with host ' . $this->_params['host'] . + ' [' . $errstr . ' #' . $errno . ']' + ); + } + if (!empty($this->_params['blocking'])) + { + stream_set_blocking($this->_stream, 1); + } + else + { + stream_set_blocking($this->_stream, 0); + } + $this->_in =& $this->_stream; + $this->_out =& $this->_stream; + } + + /** + * Opens a process for input/output. + * @access private + */ + private function _establishProcessConnection() + { + $command = $this->_params['command']; + $descriptorSpec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ); + $this->_stream = proc_open($command, $descriptorSpec, $pipes); + stream_set_blocking($pipes[2], 0); + if ($err = stream_get_contents($pipes[2])) + { + throw new Swift_TransportException( + 'Process could not be started [' . $err . ']' + ); + } + $this->_in =& $pipes[0]; + $this->_out =& $pipes[1]; + } + +} diff --git a/lib/Swift/lib/classes/Swift/TransportException.php b/lib/Swift/lib/classes/Swift/TransportException.php new file mode 100644 index 0000000..b7cd658 --- /dev/null +++ b/lib/Swift/lib/classes/Swift/TransportException.php @@ -0,0 +1,31 @@ + register('cache') + -> asAliasOf('cache.array') + + -> register('tempdir') + -> asValue('/tmp') + + -> register('cache.null') + -> asSharedInstanceOf('Swift_KeyCache_NullKeyCache') + + -> register('cache.array') + -> asSharedInstanceOf('Swift_KeyCache_ArrayKeyCache') + -> withDependencies(array('cache.inputstream')) + + -> register('cache.disk') + -> asSharedInstanceOf('Swift_KeyCache_DiskKeyCache') + -> withDependencies(array('cache.inputstream', 'tempdir')) + + -> register('cache.inputstream') + -> asNewInstanceOf('Swift_KeyCache_SimpleKeyCacheInputStream') + + ; diff --git a/lib/Swift/lib/dependency_maps/mime_deps.php b/lib/Swift/lib/dependency_maps/mime_deps.php new file mode 100644 index 0000000..e03927a --- /dev/null +++ b/lib/Swift/lib/dependency_maps/mime_deps.php @@ -0,0 +1,97 @@ + register('properties.charset') + -> asValue('utf-8') + + -> register('mime.message') + -> asNewInstanceOf('Swift_Mime_SimpleMessage') + -> withDependencies(array( + 'mime.headerset', + 'mime.qpcontentencoder', + 'cache', + 'properties.charset' + )) + + -> register('mime.part') + -> asNewInstanceOf('Swift_Mime_MimePart') + -> withDependencies(array( + 'mime.headerset', + 'mime.qpcontentencoder', + 'cache', + 'properties.charset' + )) + + -> register('mime.attachment') + -> asNewInstanceOf('Swift_Mime_Attachment') + -> withDependencies(array( + 'mime.headerset', + 'mime.base64contentencoder', + 'cache' + )) + -> addConstructorValue($swift_mime_types) + + -> register('mime.embeddedfile') + -> asNewInstanceOf('Swift_Mime_EmbeddedFile') + -> withDependencies(array( + 'mime.headerset', + 'mime.base64contentencoder', + 'cache' + )) + -> addConstructorValue($swift_mime_types) + + -> register('mime.headerfactory') + -> asNewInstanceOf('Swift_Mime_SimpleHeaderFactory') + -> withDependencies(array( + 'mime.qpheaderencoder', + 'mime.rfc2231encoder', + 'properties.charset' + )) + + -> register('mime.headerset') + -> asNewInstanceOf('Swift_Mime_SimpleHeaderSet') + -> withDependencies(array('mime.headerfactory', 'properties.charset')) + + -> register('mime.qpheaderencoder') + -> asNewInstanceOf('Swift_Mime_HeaderEncoder_QpHeaderEncoder') + -> withDependencies(array('mime.charstream')) + + -> register('mime.charstream') + -> asNewInstanceOf('Swift_CharacterStream_NgCharacterStream') + -> withDependencies(array('mime.characterreaderfactory', 'properties.charset')) + + -> register('mime.bytecanonicalizer') + -> asSharedInstanceOf('Swift_StreamFilters_ByteArrayReplacementFilter') + -> addConstructorValue(array(array(0x0D, 0x0A), array(0x0D), array(0x0A))) + -> addConstructorValue(array(array(0x0A), array(0x0A), array(0x0D, 0x0A))) + + -> register('mime.characterreaderfactory') + -> asSharedInstanceOf('Swift_CharacterReaderFactory_SimpleCharacterReaderFactory') + + -> register('mime.qpcontentencoder') + -> asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder') + -> withDependencies(array('mime.charstream', 'mime.bytecanonicalizer')) + + -> register('mime.7bitcontentencoder') + -> asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + -> addConstructorValue('7bit') + -> addConstructorValue(true) + + -> register('mime.8bitcontentencoder') + -> asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + -> addConstructorValue('8bit') + -> addConstructorValue(true) + + -> register('mime.base64contentencoder') + -> asSharedInstanceOf('Swift_Mime_ContentEncoder_Base64ContentEncoder') + + -> register('mime.rfc2231encoder') + -> asNewInstanceOf('Swift_Encoder_Rfc2231Encoder') + -> withDependencies(array('mime.charstream')) + + ; + +unset($swift_mime_types); diff --git a/lib/Swift/lib/dependency_maps/transport_deps.php b/lib/Swift/lib/dependency_maps/transport_deps.php new file mode 100644 index 0000000..32881d6 --- /dev/null +++ b/lib/Swift/lib/dependency_maps/transport_deps.php @@ -0,0 +1,62 @@ + register('transport.smtp') + -> asNewInstanceOf('Swift_Transport_EsmtpTransport') + -> withDependencies(array( + 'transport.buffer', + array('transport.authhandler'), + 'transport.eventdispatcher' + )) + + -> register('transport.sendmail') + -> asNewInstanceOf('Swift_Transport_SendmailTransport') + -> withDependencies(array( + 'transport.buffer', + 'transport.eventdispatcher' + )) + + -> register('transport.mail') + -> asNewInstanceOf('Swift_Transport_MailTransport') + -> withDependencies(array('transport.mailinvoker', 'transport.eventdispatcher')) + + -> register('transport.loadbalanced') + -> asNewInstanceOf('Swift_Transport_LoadBalancedTransport') + + -> register('transport.failover') + -> asNewInstanceOf('Swift_Transport_FailoverTransport') + + -> register('transport.mailinvoker') + -> asSharedInstanceOf('Swift_Transport_SimpleMailInvoker') + + -> register('transport.buffer') + -> asNewInstanceOf('Swift_Transport_StreamBuffer') + -> withDependencies(array('transport.replacementfactory')) + + -> register('transport.authhandler') + -> asNewInstanceOf('Swift_Transport_Esmtp_AuthHandler') + -> withDependencies(array( + array( + 'transport.crammd5auth', + 'transport.loginauth', + 'transport.plainauth' + ) + )) + + -> register('transport.crammd5auth') + -> asNewInstanceOf('Swift_Transport_Esmtp_Auth_CramMd5Authenticator') + + -> register('transport.loginauth') + -> asNewInstanceOf('Swift_Transport_Esmtp_Auth_LoginAuthenticator') + + -> register('transport.plainauth') + -> asNewInstanceOf('Swift_Transport_Esmtp_Auth_PlainAuthenticator') + + -> register('transport.eventdispatcher') + -> asNewInstanceOf('Swift_Events_SimpleEventDispatcher') + + -> register('transport.replacementfactory') + -> asSharedInstanceOf('Swift_StreamFilters_StringReplacementFilterFactory') + + ; diff --git a/lib/Swift/lib/mime_types.php b/lib/Swift/lib/mime_types.php new file mode 100644 index 0000000..65c9aa0 --- /dev/null +++ b/lib/Swift/lib/mime_types.php @@ -0,0 +1,76 @@ + 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'avi' => 'video/avi', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bz2', + 'csv' => 'text/csv', + 'dmg' => 'application/x-apple-diskimage', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'eml' => 'message/rfc822', + 'aps' => 'application/postscript', + 'exe' => 'application/x-ms-dos-executable', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/x-gzip', + 'hqx' => 'application/stuffit', + 'htm' => 'text/html', + 'html' => 'text/html', + 'jar' => 'application/x-java-archive', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'm3u' => 'audio/x-mpegurl', + 'm4a' => 'audio/mp4', + 'mdb' => 'application/x-msaccess', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'odg' => 'vnd.oasis.opendocument.graphics', + 'odp' => 'vnd.oasis.opendocument.presentation', + 'odt' => 'vnd.oasis.opendocument.text', + 'ods' => 'vnd.oasis.opendocument.spreadsheet', + 'ogg' => 'audio/ogg', + 'pdf' => 'application/pdf', + 'png' => 'image/png', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'rar' => 'application/x-rar-compressed', + 'rtf' => 'application/rtf', + 'tar' => 'application/x-tar', + 'sit' => 'application/x-stuffit', + 'svg' => 'image/svg+xml', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'ttf' => 'application/x-font-truetype', + 'txt' => 'text/plain', + 'vcf' => 'text/x-vcard', + 'wav' => 'audio/wav', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'audio/x-ms-wmv', + 'xls' => 'application/excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'zip' => 'application/zip' +); diff --git a/lib/Swift/lib/preferences.php b/lib/Swift/lib/preferences.php new file mode 100644 index 0000000..0b9e4b1 --- /dev/null +++ b/lib/Swift/lib/preferences.php @@ -0,0 +1,20 @@ +setCharset('utf-8'); + +// Without these lines the default caching mechanism is "array" but this uses +// a lot of memory. +// If possible, use a disk cache to enable attaching large attachments etc +if (function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir())) +{ + Swift_Preferences::getInstance() + -> setTempDir(sys_get_temp_dir()) + -> setCacheType('disk'); +} diff --git a/lib/Swift/lib/swift_init.php b/lib/Swift/lib/swift_init.php new file mode 100644 index 0000000..fe624a9 --- /dev/null +++ b/lib/Swift/lib/swift_init.php @@ -0,0 +1,21 @@ +dump() should be used for output where needed. Will address this again + later. diff --git a/lib/Swift/test-suite/LICENSE b/lib/Swift/test-suite/LICENSE new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/lib/Swift/test-suite/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/lib/Swift/test-suite/README b/lib/Swift/test-suite/README new file mode 100644 index 0000000..6348255 --- /dev/null +++ b/lib/Swift/test-suite/README @@ -0,0 +1,159 @@ +Sweety SimpleTest Suite +----------------------- + +Sweety is a wrapper around SimpleTest's XML reporting capabilities which +makes unit tests easier to manage and friendlier to run. + +Tests are run in a grouped fashion, but each individual test runs in its own +environment and own memory space either via forking new PHP processes, or by +making new HTTP requests. + +Sweety works with any vanilla version of SimpleTest since the XmlReporter was +added. + +Tests can be run on command line, in an AJAX equipped web browser*, or in a +web browser with javascript turned off. + + * Sweety has been tested with success in the following browsers: + + - Mozilla Firefox 2.0 + - Safari 3-beta + - Internet Explorer 7 + - Opera 9 + + +Configuring Sweety: +-------------------- + +All Sweety configuration is contained inside the config.php file, defined as +constants for the mostpart. + +Make sure you at least indicate a path to a directory containing SimpleTest, +and also change the SWEETY_INCLUDE_PATH and SWEETY_TEST_PATH to fit your needs. + +Paths are provided using the directory separator for your OS. Use the PHP +constant PATH_SEPARATOR if you need to run in different environments. + +If you have test cases in directories /webdev/tests/unit and +/webdev/tests/integration your SWEETY_TEST_PATH should look like: + +define('SWEETY_TEST_PATH', '/webdev/tests/unit' . PATH_SEPARATOR . + '/webdev/tests/integration'); + +If you want to run Sweety on the command line you'll need to specify the path +to your PHP executable (typically /usr/bin/php). Sweety needs to be able to +fork new processes using this executable. + + +What to do if your naming scheme doesn't use PEAR conventions: +-------------------------------------------------------------- + +By default Sweety looks for classes using PEAR naming conventions. If you use +some other naming convention you need to tell Sweety how to find your test cases. + +This is a two step process: + +1) Write a new Sweety_TestLocator -- don't worry, it's easy! + + Refer to the interface in lib/Sweety/TestLocator.php for guidance on what + your TestLocator needs to include (just two methods for searching and including). + +2) Add it to your config.php. + + Once you've written a new TestLocator which works for your naming scheme, + change the config value SWEETY_TEST_LOCATOR to the name of your new class, then + include the class file somewhere inside the config.php. If you use multiple class + naming conventions, list your TestLocators as a comma separated string. + + +Making tests appear in Sweety's interface: +------------------------------------------- + +No really, you just edit the configuration and they'll show up if a TestLocator +can find them ;) + + +Running sweety on the command line: +----------------------------------- + +Interacting with Sweety on the command line you'll get almost as much detail +as you do in a web browser, although the formatting obviously isn't so pretty! + +All operations are handled by the file named run.php in the sweety installation +directory. + + -bash$ php run.php #runs all tests + -bash$ php run.php Name_Of_TestClass #runs a single test case + -bash$ php run.php Name_Of_TestClass xml #runs a single test case in XML + + + +Runing Sweety with AJAX: +------------------------- + +Open up an AJAX equipped web browsers (preferably supporting DOM 3 XPath, but at +least support basic DOM). Navigate to the index.php file at the installation +directory of Sweety. You'll see the screen is divided into two sections, left +and right. On the left there's a list of test cases which you can click to run. +On the right you get all the verbose output from running the tests. + +Clicking the "Run Tests" button will run all tests you can currently see in +the list. As each test runs, a request is sent to the web server to get +SimpleTest to run your test case. If the test passes the test case will turn +green, if it fails it will turn red. Tests go yellow until a final conclusion +is drawn. + +If you need to stop the tests at any time just click the button again (it +should say "Stop Tests" whilst the tests run). + +Whilst the tests run, the large bar to the right of the screen will tally +up aggregated results and will eventually go either green or red indicating +a pass or failure. Failed assertion messages will appear in the page just like +they do with the HtmlReporter of SimpleTest. + +Clicking a single test case will run just that test in isolation. If you want +(or need?) to run the test with SimpleTest's HtmlReporter just click the +HTML Icon (little world image) next to the test case. Tests can also be run in +XML if needed. + +Above the list of tests there's a filter box which can be directly typed into. +Typing in here narrows down the list of testcases to show only the ones which +match the search query. + +Refreshing the page with your browser's refresh button will reset the test suite. + +When you change your code, you DO NOT need to refesh your browser window. Just +click the "Run Tests" button, or click the individual test to refresh the results. + +If you add a new test case you will need to refresh your browser window however. + + +Running Sweety without JavaScript: +---------------------------------- + +If your web browser has JavaScript disabled you can still use the HTML version +of the test suite. Open up the index.php file in your web browser. + +You'll see the screen is divided into two sections, left +and right. On the left there's a list of test cases with checkboxes next to them +which you can check to run. On the right you get all the verbose output from +running the tests. + +Clicking the "Run Tests" button will run all the currently selected test cases. +This could take a long time depending upon how many tests you have to run, +but once complete you'll see the page again where the tests you selected will +either be red or green indicating a pass or failure. The bar at the right of +the screen will contain aggregate results for all the tests and will be either +red or green to indicate an overall pass or failure. + +Assertion messages appear in the page just like with SimpleTest's HtmlReporter. + +If you want to run just a single test case, click the "Run" icon at the right +of the test (little running man image). + +You can run tests with SimpleTest's HtmlReporter by clicking the HTML icon +(little world image) next to the test case. Tests can be run in XML if needed +too. + +Enjoy! + diff --git a/lib/Swift/test-suite/config.php b/lib/Swift/test-suite/config.php new file mode 100644 index 0000000..20d0ebc --- /dev/null +++ b/lib/Swift/test-suite/config.php @@ -0,0 +1,65 @@ +setReporter(new Sweety_Reporter_HtmlReporter()); + +$runner->setIgnoredClassRegex(SWEETY_IGNORED_CLASSES); + +$locators = preg_split('/\s*,\s*/', SWEETY_TEST_LOCATOR); +foreach ($locators as $locator) +{ + $runner->registerTestLocator(new $locator()); +} + +if (isset($_GET['test'])) +{ + $testName = $_GET['test']; + $format = isset($_GET['format']) ? $_GET['format'] : Sweety_Runner::REPORT_HTML; + + $runner->runTestCase($testName, $format); +} +else +{ + $runner->runAllTests(); +} diff --git a/lib/Swift/test-suite/lib/Sweety/Reporter.php b/lib/Swift/test-suite/lib/Sweety/Reporter.php new file mode 100644 index 0000000..345bdc0 --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/Reporter.php @@ -0,0 +1,69 @@ +_name = $name; + $this->_aggregates = array( + 'cases' => 0, + 'run' => 0, + 'passes' => 0, + 'fails' => 0, + 'exceptions' => 0 + ); + } + + /** + * Used so test case reporters can notify this reporter when they've completed. + * @param string $testCase + */ + public function notifyEnded($testCase) + { + $this->_aggregates['run']++; + } + + /** + * Get the reporter used to report on this specific test case. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + $this->_aggregates['cases']++; + + $reporter = new Sweety_Reporter_CliTestCaseReporter($testCase, $this); + return $reporter; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + echo $this->_name . PHP_EOL; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + echo " \033[34m\033[1m\033[4mSkip\033[0m:"; + $messageLines = explode(PHP_EOL, wordwrap($message, 74, PHP_EOL)); + foreach ($messageLines as $line) + { + echo ' ' . $line . PHP_EOL; + } + echo ' in: ' . $path . PHP_EOL; + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_aggregates['passes']++; + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_aggregates['fails']++; + + echo "\033[31m" . $this->_aggregates['fails'] . ') '; + echo $message . "\033[0m" . PHP_EOL; + echo ' in: ' . $path . PHP_EOL; + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_aggregates['exceptions']++; + + echo "\033[31m\033[1mException" . $this->_aggregates['exceptions'] . "\033[0m!" . PHP_EOL; + echo "\033[1m" . $message . "\033[0m" . PHP_EOL; + echo ' in ' . $path . PHP_EOL; + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + if (preg_match('/^\{image @ (.*?)\}$/D', $output, $matches)) + { + echo " \033[33mSmoke Test\033[0m" . PHP_EOL; + echo ' Compare email sent with image @ ' . $matches[1] . PHP_EOL; + } + else + { + echo '--------------------' . PHP_EOL; + echo $output . PHP_EOL; + echo '--------------------' . PHP_EOL; + } + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + $incomplete = $this->_aggregates['cases'] - $this->_aggregates['run']; + + if ($incomplete) + { + echo '**********************' . PHP_EOL; + echo $incomplete . ' test case(s) did not complete.' . PHP_EOL . + 'This may be because invalid XML was output during the test run' . PHP_EOL . + 'and/or because an error occured.' . PHP_EOL . + 'Try running the tests separately for more detail.' . PHP_EOL; + echo '**********************' . PHP_EOL; + } + + $success = (!$this->_aggregates['fails'] && !$this->_aggregates['exceptions'] + && $this->_aggregates['cases'] == $this->_aggregates['run']); + + if ($success) + { + echo "\033[32m\033[1mOK\033[0m" . PHP_EOL; + } + else + { + echo "\033[31m\033[1mFAILURES!!!\033[0m" . PHP_EOL; + } + + echo 'Test cases run: '; + echo $this->_aggregates['run'] . '/' . $this->_aggregates['cases'] . ', '; + echo 'Passes: ' . $this->_aggregates['passes'] . ', '; + echo 'Failures: ' . $this->_aggregates['fails'] . ', '; + echo 'Exceptions: '. $this->_aggregates['exceptions'] . PHP_EOL; + + exit((int) !$success); + } + +} diff --git a/lib/Swift/test-suite/lib/Sweety/Reporter/CliTestCaseReporter.php b/lib/Swift/test-suite/lib/Sweety/Reporter/CliTestCaseReporter.php new file mode 100644 index 0000000..ba23834 --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/Reporter/CliTestCaseReporter.php @@ -0,0 +1,160 @@ +_parent = $parent; + $this->_testCase = $testCase; + $this->_aggregates = array( + 'passes' => 0, + 'fails' => 0, + 'exceptions' => 0 + ); + } + + /** + * Get the reporter used to report on this specific test case. + * This method is stubbed only to return itself. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + return $this; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + $this->_parent->reportSkip($message, $path); + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_aggregates['passes']++; + $this->_parent->reportPass($message, $path); + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_aggregates['fails']++; + $this->_parent->reportFail($message, $path); + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_aggregates['exceptions']++; + $this->_parent->reportException($message, $path); + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + $this->_parent->reportOutput($output, $path); + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + if (!$this->_aggregates['fails'] && !$this->_aggregates['exceptions']) + { + echo ' >> ' . $this->_testCase . ' '; + echo "\033[32mOK\033[0m" . PHP_EOL; + } + else + { + echo ' !! ' . $this->_testCase . ' '; + echo "\033[31mFAILED\033[0m" . PHP_EOL; + } + + $this->_parent->notifyEnded($this->_testCase); + } + +} diff --git a/lib/Swift/test-suite/lib/Sweety/Reporter/HtmlReporter.php b/lib/Swift/test-suite/lib/Sweety/Reporter/HtmlReporter.php new file mode 100644 index 0000000..6a15339 --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/Reporter/HtmlReporter.php @@ -0,0 +1,174 @@ +_tplVars =& $vars; + } + + /** + * Used so test case reporters can notify this reporter when they've completed. + * @param string $testCase + */ + public function notifyEnded($testCase) + { + $this->_tplVars['runTests'][] = $testCase; + $this->_tplVars['runCount']++; + } + + /** + * Get the reporter used to report on this specific test case. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + $this->_tplVars['caseCount']++; + + $reporter = new Sweety_Reporter_HtmlTestCaseReporter($testCase, $this); + $reporter->setTemplateVars($this->_tplVars); + return $reporter; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + $this->_tplVars['messages'][] = array( + 'type' => 'skip', + 'path' => $path, + 'text' => $message); + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_tplVars['passCount']++; + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_tplVars['failCount']++; + $this->_tplVars['messages'][] = array( + 'type' => 'fail', + 'path' => $path, + 'text' => $message); + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_tplVars['exceptionCount']++; + $this->_tplVars['messages'][] = array( + 'type' => 'exception', + 'path' => $path, + 'text' => $message); + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + $this->_tplVars['messages'][] = array( + 'type' => 'output', + 'path' => $path, + 'text' => $output); + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + if (!$this->_tplVars['failCount'] && !$this->_tplVars['exceptionCount'] + && $this->_tplVars['caseCount'] == $this->_tplVars['runCount']) + { + $this->_tplVars['result'] = 'pass'; + } + else + { + $this->_tplVars['result'] = 'fail'; + } + + $incomplete = $this->_tplVars['caseCount'] - $this->_tplVars['runCount']; + + if (0 < $incomplete) + { + $this->_tplVars['messages'][] = array( + 'type' => 'internal', + 'path' => '', + 'text' => $incomplete . ' test case(s) did not complete.' . + ' This may be because invalid XML was output during the test run' . + ' and/or because an error occured.' . + ' Incomplete test cases are shown in yellow. Click the HTML link ' . + 'next to the test for more detail.' + ); + } + } + +} diff --git a/lib/Swift/test-suite/lib/Sweety/Reporter/HtmlTestCaseReporter.php b/lib/Swift/test-suite/lib/Sweety/Reporter/HtmlTestCaseReporter.php new file mode 100644 index 0000000..8d4474d --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/Reporter/HtmlTestCaseReporter.php @@ -0,0 +1,174 @@ +_parent = $parent; + $this->_testCase = $testCase; + $this->_aggregates = array( + 'passes' => 0, + 'fails' => 0, + 'exceptions' => 0 + ); + } + + /** + * Set template data. + * @param mixed[] + */ + public function setTemplateVars(&$vars) + { + $this->_tplVars =& $vars; + } + + /** + * Get the reporter used to report on this specific test case. + * This method is stubbed only to return itself. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + return $this; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + $this->_tplVars['runTests'][$this->_testCase] = 'running'; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + $this->_parent->reportSkip($message, $path); + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_aggregates['passes']++; + $this->_parent->reportPass($message, $path); + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_aggregates['fails']++; + $this->_parent->reportFail($message, $path); + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_aggregates['exceptions']++; + $this->_parent->reportException($message, $path); + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + $this->_parent->reportOutput($output, $path); + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + if (!$this->_aggregates['fails'] && !$this->_aggregates['exceptions']) + { + $this->_tplVars['runTests'][$this->_testCase] = 'pass'; + } + else + { + $this->_tplVars['runTests'][$this->_testCase] = 'fail'; + } + + $this->_parent->notifyEnded($this->_testCase); + } + +} diff --git a/lib/Swift/test-suite/lib/Sweety/Runner.php b/lib/Swift/test-suite/lib/Sweety/Runner.php new file mode 100644 index 0000000..b07ddb4 --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/Runner.php @@ -0,0 +1,56 @@ +_reporter = $reporter; + } + + /** + * Get the reporter used for showing results. + * @return Sweety_Reporter + */ + public function getReporter() + { + return $this->_reporter; + } + + /** + * Register a test locator instance. + * @param Sweety_TestLocator $locator + */ + public function registerTestLocator(Sweety_TestLocator $locator) + { + $this->_testLocators[] = $locator; + } + + /** + * Set the regular expression used to filter out certain class names. + * @param string $ignoredClassRegex + */ + public function setIgnoredClassRegex($ignoredClassRegex) + { + $this->_ignoredClassRegex = $ignoredClassRegex; + } + + /** + * Get the filtering regular expression for ignoring certain classes. + * @return string + */ + public function getIgnoredClassRegex() + { + return $this->_ignoredClassRegex; + } + + /** + * Run a single test case with the given name, using the provided output format. + * @param string $testName + * @param string $format (xml, text or html) + * @return int + */ + public function runTestCase($testName, $format = Sweety_Runner::REPORT_TEXT) + { + foreach ($this->_testLocators as $locator) + { + if ($locator->includeTest($testName)) + { + break; + } + } + + $testClass = new ReflectionClass($testName); + if ($testClass->getConstructor()) + { + //We don't want test output to be cached + if (!SimpleReporter::inCli()) + { + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + } + + switch ($format) + { + case Sweety_Runner::REPORT_HTML: + $reporter = new HtmlReporter(); + break; + case Sweety_Runner::REPORT_XML: + if (!SimpleReporter::inCli()) + { + header("Content-Type: text/xml"); //Sigh! SimpleTest (skip() issues). + } + $reporter = new XmlReporter(); + break; + case Sweety_Runner::REPORT_TEXT: + default: + $reporter = new TextReporter(); + break; + } + $test = $testClass->newInstance(); + return $test->run($reporter) ? 0 : 1; + } + + return 1; + } + + /** + * Use strategies to find tests which are runnable. + * @param string[] $dirs + * @return string[] + */ + protected function findTests($dirs = array()) + { + $tests = array(); + foreach ($this->_testLocators as $locator) + { + $tests += $locator->getTests($dirs); + } + return $tests; + } + + /** + * Parse an XML response from a test case an report to the reporter. + * @param string $xml + * @param string $testCase + */ + protected function parseXml($xml, $testCase) + { + $reporter = $this->_reporter->getReporterFor($testCase); + if (!$reporter->isStarted()) + { + $reporter->start(); + } + + $xml = str_replace("\0", '?', trim($xml)); + $xml = preg_replace('/[^\x01-\x7F]/e', 'sprintf("&#%d;", ord("$0"));', $xml); //Do something better? + if (!empty($xml)) + { + $document = @simplexml_load_string($xml); + if ($document) + { + $this->_parseDocument($document, $testCase, $reporter); + $reporter->finish(); + return; + } + } + + $reporter->reportException( + 'Invalid XML response: ' . + trim(strip_tags( + preg_replace('/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/s', '', $xml) + )), + $testCase + ); + } + + /** + * Parse formatted test output. + * @param SimpleXMLElement The node containing the output + * @param string $path to this test method + * @access private + */ + private function _parseFormatted(SimpleXMLElement $formatted, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportOutput((string)$formatted, $path); + } + + /** + * Parse test output. + * @param SimpleXMLElement The node containing the output + * @param string $path to this test method + * @access private + */ + private function _parseMessage(SimpleXMLElement $message, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportOutput((string)$message, $path); + } + + /** + * Parse a test failure. + * @param SimpleXMLElement The node containing the fail + * @param string $path to this test method + * @access private + */ + private function _parseFailure(SimpleXMLElement $failure, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportFail((string)$failure, $path); + } + + /** + * Parse an exception. + * @param SimpleXMLElement The node containing the exception + * @param string $path to this test method + * @access private + */ + private function _parseException(SimpleXMLElement $exception, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportException((string)$exception, $path); + } + + /** + * Parse a pass. + * @param SimpleXMLElement The node containing this pass. + * @param string $path to this test method + * @access private + */ + private function _parsePass(SimpleXMLElement $pass, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportPass((string)$pass, $path); + } + + /** + * Parse a single test case. + * @param SimpleXMLElement The node containing the test case + * @param string $path to this test case + * @access private + */ + private function _parseTestCase(SimpleXMLElement $testCase, $path = '', + Sweety_Reporter $reporter) + { + foreach ($testCase->xpath('./test') as $testMethod) + { + $testMethodName = (string) $this->_firstNodeValue($testMethod->xpath('./name')); + + foreach ($testMethod->xpath('./formatted') as $formatted) + { + $this->_parseFormatted( + $formatted, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./message') as $message) + { + $this->_parseMessage( + $message, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./fail') as $failure) + { + $this->_parseFailure( + $failure, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./exception') as $exception) + { + $this->_parseException( + $exception, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./pass') as $pass) + { + $this->_parsePass($pass, $path . ' -> ' . $testMethodName, $reporter); + } + + $stdout = trim((string) $testMethod); + if ($stdout) + { + $reporter->reportOutput($stdout, $path . ' -> ' . $testMethodName); + } + } + } + + /** + * Parse the results of all tests. + * @param SimpleXMLElement The node containing the tests + * @param string $path to the tests + * @access private + */ + private function _parseResults(SimpleXMLElement $document, $path = '', + Sweety_Reporter $reporter) + { + $groups = $document->xpath('./group'); + if (!empty($groups)) + { + foreach ($groups as $group) + { + $groupName = (string) $this->_firstNodeValue($group->xpath('./name')); + $this->_parseResults($group, $path . ' -> ' . $groupName, $reporter); + } + } + else + { + foreach ($document->xpath('./case') as $testCase) + { + $this->_parseTestCase($testCase, $path, $reporter); + } + } + } + + /** + * Parse the entire SimpleTest XML document from a test case. + * @param SimpleXMLElement $document to parse + * @param string $path to the test + * @access private + */ + private function _parseDocument(SimpleXMLElement $document, $path = '', + Sweety_Reporter $reporter) + { + if ($everything = $this->_firstNodeValue($document->xpath('/run'))) + { + $this->_parseResults($everything, $path, $reporter); + } + elseif ($skip = $this->_firstNodeValue($document->xpath('/skip'))) + { + $reporter->reportSkip((string) $skip, $path); + } + } + + protected function _sort($a, $b) + { + $apkg = preg_replace('/_[^_]+$/D', '', $a); + $bpkg = preg_replace('/_[^_]+$/D', '', $b); + if ($apkg == $bpkg) + { + if ($a == $b) + { + return 0; + } + else + { + return ($a > $b) ? 1 : -1; + } + } + else + { + return ($apkg > $bpkg) ? 1 : -1; + } + } + + private function _firstNodeValue($nodeSet) + { + $first = array_shift($nodeSet); + return $first; + } + +} diff --git a/lib/Swift/test-suite/lib/Sweety/Runner/CliRunner.php b/lib/Swift/test-suite/lib/Sweety/Runner/CliRunner.php new file mode 100644 index 0000000..c3e4026 --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/Runner/CliRunner.php @@ -0,0 +1,128 @@ +_dirs = $dirs; + $this->_command = $command; + } + + /** + * Runs all test cases found under the given directories. + * @param string[] $directories to scan for test cases + * @param string To be prepended to class names + * @return int + */ + public function runAllTests($dirs = array()) + { + if (empty($dirs)) + { + $dirs = $this->_dirs; + } + + $reporter = $this->getReporter(); + + if (!$reporter->isStarted()) + { + $reporter->start(); + } + + $tests = $this->findTests($dirs); + usort($tests, array($this, '_sort')); + + global $argv; + + if (!empty($argv[1])) + { + if (substr($argv[1], 0, 1) == '!') + { + $argv[1] = substr($argv[1], 1); + foreach ($tests as $index => $name) + { + if (@preg_match($argv[1] . 'i', $name)) + { + unset($tests[$index]); + } + } + } + else + { + foreach ($tests as $index => $name) + { + if (!@preg_match($argv[1] . 'i', $name)) + { + unset($tests[$index]); + } + } + } + } + + $ret = $this->_runTestList($tests); + + $reporter->finish(); + + return $ret; + } + + /** + * Run all possible tests from the given list. + * @param string[] $tests + * @return int + */ + protected function _runTestList(array $tests) + { + foreach ($tests as $testCase) + { + if (preg_match($this->getIgnoredClassRegex(), $testCase)) + { + continue; + } + + $command = $this->_command; + $command .= ' ' . $testCase; + $command .= ' ' . Sweety_Runner::REPORT_XML; + + exec($command, $output, $status); + + $xml = implode(PHP_EOL, $output); + + $this->parseXml($xml, $testCase); + + unset($status); + unset($output); + } + + return 0; + } + +} diff --git a/lib/Swift/test-suite/lib/Sweety/Runner/HtmlRunner.php b/lib/Swift/test-suite/lib/Sweety/Runner/HtmlRunner.php new file mode 100644 index 0000000..e115692 --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/Runner/HtmlRunner.php @@ -0,0 +1,160 @@ +_dirs = $dirs; + $this->_template = $template; + $this->_name = $name; + } + + /** + * Runs all test cases found under the given directories. + * @param string[] $directories to scan for test cases + * @param string To be prepended to class names + * @return int + */ + public function runAllTests($dirs = array()) + { + //We don't want test output to be cached + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + + if (empty($dirs)) + { + $dirs = $this->_dirs; + } + + $testCases = $this->findTests($dirs); + foreach ($testCases as $k => $testCase) + { + if (preg_match($this->getIgnoredClassRegex(), $testCase)) + { + unset($testCases[$k]); + } + } + + usort($testCases, array($this, '_sort')); + + $vars = array( + //String + 'testCases' => $testCases, + //String + 'suiteName' => $this->_name, + // testCase => pass | fail | running + 'runTests' => array(), + //Integer + 'caseCount' => 0, + //Integer + 'runCount' => 0, + //Integer + 'passCount' => 0, + //Integer + 'failCount' => 0, + //Integer + 'exceptionCount' => 0, + // type => pass | fail | exception | output | internal, path => testCase, text => ... + 'messages' => array(), + // pass | fail + 'result' => 'idle' + ); + + if (isset($_REQUEST['runtests'])) + { + $reporter = $this->getReporter(); + $reporter->setTemplateVars($vars); + + if (!$reporter->isStarted()) + { + $reporter->start(); + } + + $this->_runTestList((array)$_REQUEST['runtests'], $reporter); + + $reporter->finish(); + } + else + { + foreach ($testCases as $testCase) + { + $vars['runTests'][$testCase] = 'idle'; //Show all checked by default + } + } + + $this->_render($vars); + } + + /** + * Run tests in the given array using the REST API (kind of). + * @param string[] $tests + * @param Sweety_Reporter $reporter + * @return int + */ + protected function _runTestList(array $tests, Sweety_Reporter $reporter) + { + $protocol = !empty($_SERVER['HTTPS']) ? 'https://' : 'http://'; + + //Most likely a HTTP/1.0 server not supporting HOST header + $server = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '127.0.0.1'; + + $path = '/'; + if (!empty($_SERVER['REQUEST_URI'])) + { + $path = preg_replace('/\?.*$/sD', '', $_SERVER['REQUEST_URI']); + } + + $baseUrl = $protocol . $server . $path; + + foreach ($tests as $testCase) + { + $url = $baseUrl . '?test=' . $testCase . '&format=xml'; + $xml = file_get_contents($url); + $this->parseXml($xml, $testCase); + } + + return 0; + } + + /** + * Renders the view for the suite. + * @param string[] $templateVars + */ + protected function _render($vars = array()) + { + foreach ($vars as $k => $v) + { + $$k = $v; + } + + require_once $this->_template; + } + +} diff --git a/lib/Swift/test-suite/lib/Sweety/TestLocator.php b/lib/Swift/test-suite/lib/Sweety/TestLocator.php new file mode 100644 index 0000000..96e4649 --- /dev/null +++ b/lib/Swift/test-suite/lib/Sweety/TestLocator.php @@ -0,0 +1,25 @@ +_findTestCases($dirs); + } + + public function includeTest($testCase) + { + $file = str_replace('_', '/', $testCase) . '.php'; + foreach (explode(PATH_SEPARATOR, get_include_path()) as $dir) + { + if (is_file($dir . '/' . $file)) + { + require_once $dir . '/' . $file; + return true; + } + } + + return false; + } + + protected function _findTestCases($dirs = array(), $prepend = '') + { + $ret = array(); + + foreach ($dirs as $dir) + { + if (array_key_exists($dir, $this->_testCache)) + { + $ret += $this->_testCache[$dir]; + continue; + } + + $this->_testCache[$dir] = array(); + + $handle = opendir($dir); + while (false !== $file = readdir($handle)) + { + if (substr($file, 0, 1) != '.' && is_dir($dir . '/' . $file)) + { + foreach ($this->_findTestCases( + array($dir . '/' . $file), $prepend . $file . '_') as $add) + { + $this->_testCache[$dir][] = $add; + $ret[] = $add; + } + } + elseif (substr($file, -4) == '.php') + { + $className = $prepend . basename($file, '.php'); + $this->_testCache[$dir][] = $className; + $ret[] = $className; + } + } + closedir($handle); + } + + sort($ret); + + return $ret; + } + +} diff --git a/lib/Swift/test-suite/lib/simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE b/lib/Swift/test-suite/lib/simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE new file mode 100644 index 0000000..d99acd8 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE @@ -0,0 +1,383 @@ +Simple Test interface changes +============================= +Because the SimpleTest tool set is still evolving it is likely that tests +written with earlier versions will fail with the newest ones. The most +dramatic changes are in the alpha releases. Here is a list of possible +problems and their fixes... + +Fatal error: Call to undefined method Classname::classname() +------------------------------------------------------------ +SimpleTest renamed all of its constructors from +Classname to __construct; derived classes invoking +their parent constructors should replace parent::Classname() +with parent::__construct(). + +Custom CSS in HtmlReporter not being applied +-------------------------------------------- +Batch rename of protected and private methods +means that _getCss() was renamed to getCss(); +replace your function definition accordingly. + +setReturnReference() throws errors in E_STRICT +---------------------------------------------- +Happens when an object is passed by reference. +This also happens with setReturnReferenceAt(). +If you want to return objects then replace these +with calls to returns() and returnsAt() with the +same arguments. This change was forced in the 1.1 +version for E_STRICT compatibility. + +assertReference() throws errors in E_STRICT +------------------------------------------- +Due to language restrictions you cannot compare +both variables and objects in E_STRICT mode. Use +assertSame() in this mode with objects. This change +was forced the 1.1 version. + +Cannot create GroupTest +----------------------- +The GroupTest has been renamed TestSuite (see below). +It was removed completely in 1.1 in favour of this +name. + +No method getRelativeUrls() or getAbsoluteUrls() +------------------------------------------------ +These methods were always a bit weird anyway, and +the new parsing of the base tag makes them more so. +They have been replaced with getUrls() instead. If +you want the old functionality then simply chop +off the current domain from getUrl(). + +Method setWildcard() removed in mocks +------------------------------------- +Even setWildcard() has been removed in 1.0.1beta now. +If you want to test explicitely for a '*' string, then +simply pass in new IdenticalExpectation('*') instead. + +No method _getTest() on mocks +----------------------------- +This has finally been removed. It was a pretty esoteric +flex point anyway. It was there to allow the mocks to +work with other test tools, but no one does this. + +No method assertError(), assertNoErrors(), swallowErrors() +---------------------------------------------------------- +These have been deprecated in 1.0.1beta in favour of +expectError() and expectException(). assertNoErrors() is +redundant if you use expectError() as failures are now reported +immediately. + +No method TestCase::signal() +---------------------------- +This has been deprecated in favour of triggering an error or +throwing an exception. Deprecated as of 1.0.1beta. + +No method TestCase::sendMessage() +--------------------------------- +This has been deprecated as of 1.0.1beta. + +Failure to connect now emits failures +------------------------------------- +It used to be that you would have to use the +getTransferError() call on the web tester to see if +there was a socket level error in a fetch. This check +is now always carried out by the WebTestCase unless +the fetch is prefaced with WebTestCase::ignoreErrors(). +The ignore directive only lasts for test case fetching +action such as get() and click(). + +No method SimpleTestOptions::ignore() +------------------------------------- +This is deprecated in version 1.0.1beta and has been moved +to SimpleTest::ignore() as that is more readable. In +addition, parent classes are also ignored automatically. +If you are using PHP5 you can skip this directive simply +by marking your test case as abstract. + +No method assertCopy() +---------------------- +This is deprecated in 1.0.1 in favour of assertClone(). +The assertClone() method is slightly different in that +the objects must be identical, but without being a +reference. It is thus not a strict inversion of +assertReference(). + +Constructor wildcard override has no effect in mocks +---------------------------------------------------- +As of 1.0.1beta this is now set with setWildcard() instead +of in the constructor. + +No methods setStubBaseClass()/getStubBaseClass() +------------------------------------------------ +As mocks are now used instead of stubs, these methods +stopped working and are now removed as of the 1.0.1beta +release. The mock objects may be freely used instead. + +No method addPartialMockCode() +------------------------------ +The ability to insert arbitrary partial mock code +has been removed. This was a low value feature +causing needless complications. It was removed +in the 1.0.1beta release. + +No method setMockBaseClass() +---------------------------- +The ability to change the mock base class has been +scheduled for removal and is deprecated since the +1.0.1beta version. This was a rarely used feature +except as a workaround for PHP5 limitations. As +these limitations are being resolved it's hoped +that the bundled mocks can be used directly. + +No class Stub +------------- +Server stubs are deprecated from 1.0.1 as the mocks now +have exactly the same interface. Just use mock objects +instead. + +No class SimpleTestOptions +-------------------------- +This was replced by the shorter SimpleTest in 1.0.1beta1 +and is since deprecated. + +No file simple_test.php +----------------------- +This was renamed test_case.php in 1.0.1beta to more accurately +reflect it's purpose. This file should never be directly +included in test suites though, as it's part of the +underlying mechanics and has a tendency to be refactored. + +No class WantedPatternExpectation +--------------------------------- +This was deprecated in 1.0.1alpha in favour of the simpler +name PatternExpectation. + +No class NoUnwantedPatternExpectation +------------------------------------- +This was deprecated in 1.0.1alpha in favour of the simpler +name NoPatternExpectation. + +No method assertNoUnwantedPattern() +----------------------------------- +This has been renamed to assertNoPattern() in 1.0.1alpha and +the old form is deprecated. + +No method assertWantedPattern() +------------------------------- +This has been renamed to assertPattern() in 1.0.1alpha and +the old form is deprecated. + +No method assertExpectation() +----------------------------- +This was renamed as assert() in 1.0.1alpha and the old form +has been deprecated. + +No class WildcardExpectation +---------------------------- +This was a mostly internal class for the mock objects. It was +renamed AnythingExpectation to bring it closer to JMock and +NMock in version 1.0.1alpha. + +Missing UnitTestCase::assertErrorPattern() +------------------------------------------ +This method is deprecated for version 1.0.1 onwards. +This method has been subsumed by assertError() that can now +take an expectation. Simply pass a PatternExpectation +into assertError() to simulate the old behaviour. + +No HTML when matching page elements +----------------------------------- +This behaviour has been switched to using plain text as if it +were seen by the user of the browser. This means that HTML tags +are suppressed, entities are converted and whitespace is +normalised. This should make it easier to match items in forms. +Also images are replaced with their "alt" text so that they +can be matched as well. + +No method SimpleRunner::_getTestCase() +-------------------------------------- +This was made public as getTestCase() in 1.0RC2. + +No method restartSession() +-------------------------- +This was renamed to restart() in the WebTestCase, SimpleBrowser +and the underlying SimpleUserAgent in 1.0RC2. Because it was +undocumented anyway, no attempt was made at backward +compatibility. + +My custom test case ignored by tally() +-------------------------------------- +The _assertTrue method has had it's signature changed due to a bug +in the PHP 5.0.1 release. You must now use getTest() from within +that method to get the test case. Mock compatibility with other +unit testers is now deprecated as of 1.0.1alpha as PEAR::PHPUnit2 +should soon have mock support of it's own. + +Broken code extending SimpleRunner +---------------------------------- +This was replaced with SimpleScorer so that I could use the runner +name in another class. This happened in RC1 development and there +is no easy backward compatibility fix. The solution is simply to +extend SimpleScorer instead. + +Missing method getBaseCookieValue() +----------------------------------- +This was renamed getCurrentCookieValue() in RC1. + +Missing files from the SimpleTest suite +--------------------------------------- +Versions of SimpleTest prior to Beta6 required a SIMPLE_TEST constant +to point at the SimpleTest folder location before any of the toolset +was loaded. This is no longer documented as it is now unnecessary +for later versions. If you are using an earlier version you may +need this constant. Consult the documentation that was bundled with +the release that you are using or upgrade to Beta6 or later. + +No method SimpleBrowser::getCurrentUrl() +-------------------------------------- +This is replaced with the more versatile showRequest() for +debugging. It only existed in this context for version Beta5. +Later versions will have SimpleBrowser::getHistory() for tracking +paths through pages. It is renamed as getUrl() since 1.0RC1. + +No method Stub::setStubBaseClass() +---------------------------------- +This method has finally been removed in 1.0RC1. Use +SimpleTestOptions::setStubBaseClass() instead. + +No class CommandLineReporter +---------------------------- +This was renamed to TextReporter in Beta3 and the deprecated version +was removed in 1.0RC1. + +No method requireReturn() +------------------------- +This was deprecated in Beta3 and is now removed. + +No method expectCookie() +------------------------ +This method was abruptly removed in Beta4 so as to simplify the internals +until another mechanism can replace it. As a workaround it is necessary +to assert that the cookie has changed by setting it before the page +fetch and then assert the desired value. + +No method clickSubmitByFormId() +------------------------------- +This method had an incorrect name as no button was involved. It was +renamed to submitByFormId() in Beta4 and the old version deprecated. +Now removed. + +No method paintStart() or paintEnd() +------------------------------------ +You should only get this error if you have subclassed the lower level +reporting and test runner machinery. These methods have been broken +down into events for test methods, events for test cases and events +for group tests. The new methods are... + +paintStart() --> paintMethodStart(), paintCaseStart(), paintGroupStart() +paintEnd() --> paintMethodEnd(), paintCaseEnd(), paintGroupEnd() + +This change was made in Beta3, ironically to make it easier to subclass +the inner machinery. Simply duplicating the code you had in the previous +methods should provide a temporary fix. + +No class TestDisplay +-------------------- +This has been folded into SimpleReporter in Beta3 and is now deprecated. +It was removed in RC1. + +No method WebTestCase::fetch() +------------------------------ +This was renamed get() in Alpha8. It is removed in Beta3. + +No method submit() +------------------ +This has been renamed clickSubmit() in Beta1. The old method was +removed in Beta2. + +No method clearHistory() +------------------------ +This method is deprecated in Beta2 and removed in RC1. + +No method getCallCount() +------------------------ +This method has been deprecated since Beta1 and has now been +removed. There are now more ways to set expectations on counts +and so this method should be unecessery. Removed in RC1. + +Cannot find file * +------------------ +The following public name changes have occoured... + +simple_html_test.php --> reporter.php +simple_mock.php --> mock_objects.php +simple_unit.php --> unit_tester.php +simple_web.php --> web_tester.php + +The old names were deprecated in Alpha8 and removed in Beta1. + +No method attachObserver() +-------------------------- +Prior to the Alpha8 release the old internal observer pattern was +gutted and replaced with a visitor. This is to trade flexibility of +test case expansion against the ease of writing user interfaces. + +Code such as... + +$test = &new MyTestCase(); +$test->attachObserver(new TestHtmlDisplay()); +$test->run(); + +...should be rewritten as... + +$test = &new MyTestCase(); +$test->run(new HtmlReporter()); + +If you previously attached multiple observers then the workaround +is to run the tests twice, once with each, until they can be combined. +For one observer the old method is simulated in Alpha 8, but is +removed in Beta1. + +No class TestHtmlDisplay +------------------------ +This class has been renamed to HtmlReporter in Alpha8. It is supported, +but deprecated in Beta1 and removed in Beta2. If you have subclassed +the display for your own design, then you will have to extend this +class (HtmlReporter) instead. + +If you have accessed the event queue by overriding the notify() method +then I am afraid you are in big trouble :(. The reporter is now +carried around the test suite by the runner classes and the methods +called directly. In the unlikely event that this is a problem and +you don't want to upgrade the test tool then simplest is to write your +own runner class and invoke the tests with... + +$test->accept(new MyRunner(new MyReporter())); + +...rather than the run method. This should be easier to extend +anyway and gives much more control. Even this method is overhauled +in Beta3 where the runner class can be set within the test case. Really +the best thing to do is to upgrade to this version as whatever you were +trying to achieve before should now be very much easier. + +Missing set options method +-------------------------- +All test suite options are now in one class called SimpleTestOptions. +This means that options are set differently... + +GroupTest::ignore() --> SimpleTestOptions::ignore() +Mock::setMockBaseClass() --> SimpleTestOptions::setMockBaseClass() + +These changed in Alpha8 and the old versions are now removed in RC1. + +No method setExpected*() +------------------------ +The mock expectations changed their names in Alpha4 and the old names +ceased to be supported in Alpha8. The changes are... + +setExpectedArguments() --> expectArguments() +setExpectedArgumentsSequence() --> expectArgumentsAt() +setExpectedCallCount() --> expectCallCount() +setMaximumCallCount() --> expectMaximumCallCount() + +The parameters remained the same. diff --git a/lib/Swift/test-suite/lib/simpletest/LICENSE b/lib/Swift/test-suite/lib/simpletest/LICENSE new file mode 100644 index 0000000..09f465a --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/lib/Swift/test-suite/lib/simpletest/README b/lib/Swift/test-suite/lib/simpletest/README new file mode 100644 index 0000000..f0912b2 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/README @@ -0,0 +1,108 @@ +SimpleTest +========== +You probably got this package from... +http://simpletest.sourceforge.net/projects/simpletest/ + +If there is no licence agreement with this package please download +a version from the location above. You must read and accept that +licence to use this software. The file is titled simply LICENSE. + +What is it? It's a framework for unit testing, web site testing and +mock objects for PHP 5.0.5+. + +If you have used JUnit, you will find this PHP unit testing version very +similar. Also included is a mock objects and server stubs generator. +The stubs can have return values set for different arguments, can have +sequences set also by arguments and can return items by reference. +The mocks inherit all of this functionality and can also have +expectations set, again in sequences and for different arguments. + +A web tester similar in concept to JWebUnit is also included. There is no +JavaScript or tables support, but forms, authentication, cookies and +frames are handled. + +You can see a release schedule at http://simpletest.org/en/overview.html +which is also copied to the documentation folder with this release. +A full PHPDocumenter API documentation exists at +http://simpletest.org/api/. + +The user interface is minimal +in the extreme, but a lot of information flows from the test suite. +After version 1.0 we will release a better web UI, but we are leaving XUL +and GTk versions to volunteers as everybody has their own opinion +on a good GUI, and we don't want to discourage development by shipping +one with the toolkit. You can download an Eclipse plug-in separately. + +You are looking at a second full release. The unit tests for SimpleTest +itself can be run here... + +simpletest/test/unit_tests.php + +And tests involving live network connections as well are here... + +simpletest/test/all_tests.php + +The full tests will typically overrun the 8Mb limit often allowed +to a PHP process. A workaround is to run the tests on the command +with a custom php.ini file if you do not have access to your server +version. + +You will have to edit the all_tests.php file if you are accesssing +the internet through a proxy server. See the comments in all_tests.php +for instructions. + +The full tests read some test data from the LastCraft site. If the site +is down or has been modified for a later version then you will get +spurious errors. A unit_tests.php failure on the other hand would be +very serious. As far as we know we haven't yet managed to check in any +unit test failures, so please correct us if you find one. + +Even if all of the tests run please verify that your existing test suites +also function as expected. If they don't see the file... + +HELP_MY_TESTS_DONT_WORK_ANYMORE + +This contains information on interface changes. It also points out +deprecated interfaces, so you should read this even if all of +your current tests appear to run. + +There is a documentation folder which contains the core reference information +in English and French, although this information is fairly basic. +You can find a tutorial on... + +http://simpletest.org/en/first_test_tutorial.html + +...to get you started and this material will eventually become included +with the project documentation. A French translation exists at... + +http://simpletest.org/fr/first_test_tutorial.html + +If you download and use, and possibly even extend this tool, please let us +know. Any feedback, even bad, is always welcome and we will work to get +your suggestions into the next release. Ideally please send your +comments to... + +simpletest-support@lists.sourceforge.net + +...so that others can read them too. We usually try to respond within 48 +hours. + +There is no change log except at Sourceforge. You can visit the +release notes to see the completed TODO list after each cycle and also the +status of any bugs, but if the bug is recent then it will be fixed in SVN only. +The SVN check-ins always have all the tests passing and so SVN snapshots should +be pretty usable, although the code may not look so good internally. + +Oh, yes. It is called "Simple" because it should be simple to +use. We intend to add a complete set of tools for a test first +and "test as you code" type of development. "Simple" does not +mean "Lite" in this context. + +Thanks to everyone who has sent comments and offered suggestions. They +really are invaluable, but sadly you are too many to mention in full. +Thanks to all on the advanced PHP forum on SitePoint, especially Harry +Fuecks. Early adopters are always an inspiration. + +Marcus Baker, Jason Sweat, Travis Swicegood, Perrick Penet and Edward Z. Yang. +-- +marcus@lastcraft.com diff --git a/lib/Swift/test-suite/lib/simpletest/TODO.xml b/lib/Swift/test-suite/lib/simpletest/TODO.xml new file mode 100644 index 0000000..3fa84e9 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/TODO.xml @@ -0,0 +1,176 @@ + + + TODO tasks for the current iteration + +
+

+ The following is the approximate plan for the next full point release. +

+

+ Before each release we hope to have the following done. + More may get done, depending on the interest of the volunteers, + but this is the current minimum. +

+

+ The aim of this release cycle is to produce a functionally + identical version to the 1.0.1 release, but bug-fixed and + fully compatible with PHP 5.0.5+ under E_STRICT. + We are also hoping to flush out issues and use cases + caused by people hacking against unpublished flex points + in SimpleTest. + We want to break people's code now, not while we are developing + features down the line. +

+

+ With the website move to a new server, and more developers, + we are able and need to improve the test automation and developer + cooperation. + This release is a deep drawing of breath before going forward. +

+
+
+ + + Undefined property $_reporter + fatal error + + + + + + + + + The HELP_MY_TESTS_DONT_WORK_ANYMORE needs to be updated. + + README needs to be updated. + + Write XSLT code for this file so Perrick doesn't strangle me + + + + Ensure extensions are compatible with PHP5 constructor renaming in the current trunk. + + + Update PEAR package task to be compatible with latest PEAR installer. + PHP 5.3 compatible under E_STRICT + PHP 5.2.0-5 compatible under E_STRICT + PHP 5.1.0-6 compatible under E_STRICT + continuous integration + error_reporting(E_ALL|E_STRICT)gives lots of warning + Remove all deprecated methods + + Drop underscores from protected methods and + private variables. + Make all variables private and add protected + accessors where we use them internally. + + That way people will start complaining. + Upon each complaint we'll add an accessor and + capture the use case from them. + + We'll stick the use cases in the feature request tracker for now + + Move web site to new server + + + + + + + Deprecate all mentions of GroupTest without breaking + existing code. + + Need to swap the terminology for TestSuite + in method names, etc. + + + XmlReporter generating invalid XML + + + Remove reflection facade for PHP4 + + + + label not assigned to radio and checkbox + incorrect proxy requests + + + + Docblocks need to be cut back to a minimum + + + + PHP 5.0.5 compatible under E_STRICT + Move acceptance tests sample pages to new server + + + + + + + Remove reflection facade for PHP4 + + + + + + Throw away old tutorial + + + + PHP 6 compatible under E_STRICT + + Automated nightly test script that runs tests on all + targeted PHP versions. + + + +
+
+ + + Current iteration is 1.1beta. + + + Upcoming tasks for + Unit tester, + Reporter, + Mock objects, + Parser, + Browser, + Web tester, + Documentation, + Extensions and + Build. + + + + + Trackers for : + feature requests, + bugs and + patches. + + + + + software development, + computer programmer, + php programming, + programming php, + software development company, + software development uk, + php tutorial, + bespoke software development uk, + corporate web development, + architecture, + freelancer, + php resources, + wordtracker, + web marketing, + serach engines, + web positioning, + internet marketing + + +
diff --git a/lib/Swift/test-suite/lib/simpletest/VERSION b/lib/Swift/test-suite/lib/simpletest/VERSION new file mode 100644 index 0000000..ab8bad0 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/VERSION @@ -0,0 +1 @@ +1.1beta \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/authentication.php b/lib/Swift/test-suite/lib/simpletest/authentication.php new file mode 100644 index 0000000..5af66ec --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/authentication.php @@ -0,0 +1,237 @@ +type = $type; + $this->root = $url->getBasePath(); + $this->username = false; + $this->password = false; + } + + /** + * Adds another location to the realm. + * @param SimpleUrl $url Somewhere in realm. + * @access public + */ + function stretch($url) { + $this->root = $this->getCommonPath($this->root, $url->getPath()); + } + + /** + * Finds the common starting path. + * @param string $first Path to compare. + * @param string $second Path to compare. + * @return string Common directories. + * @access private + */ + protected function getCommonPath($first, $second) { + $first = explode('/', $first); + $second = explode('/', $second); + for ($i = 0; $i < min(count($first), count($second)); $i++) { + if ($first[$i] != $second[$i]) { + return implode('/', array_slice($first, 0, $i)) . '/'; + } + } + return implode('/', $first) . '/'; + } + + /** + * Sets the identity to try within this realm. + * @param string $username Username in authentication dialog. + * @param string $username Password in authentication dialog. + * @access public + */ + function setIdentity($username, $password) { + $this->username = $username; + $this->password = $password; + } + + /** + * Accessor for current identity. + * @return string Last succesful username. + * @access public + */ + function getUsername() { + return $this->username; + } + + /** + * Accessor for current identity. + * @return string Last succesful password. + * @access public + */ + function getPassword() { + return $this->password; + } + + /** + * Test to see if the URL is within the directory + * tree of the realm. + * @param SimpleUrl $url URL to test. + * @return boolean True if subpath. + * @access public + */ + function isWithin($url) { + if ($this->isIn($this->root, $url->getBasePath())) { + return true; + } + if ($this->isIn($this->root, $url->getBasePath() . $url->getPage() . '/')) { + return true; + } + return false; + } + + /** + * Tests to see if one string is a substring of + * another. + * @param string $part Small bit. + * @param string $whole Big bit. + * @return boolean True if the small bit is + * in the big bit. + * @access private + */ + protected function isIn($part, $whole) { + return strpos($whole, $part) === 0; + } +} + +/** + * Manages security realms. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAuthenticator { + private $realms; + + /** + * Clears the realms. + * @access public + */ + function SimpleAuthenticator() { + $this->restartSession(); + } + + /** + * Starts with no realms set up. + * @access public + */ + function restartSession() { + $this->realms = array(); + } + + /** + * Adds a new realm centered the current URL. + * Browsers privatey wildly on their behaviour in this + * regard. Mozilla ignores the realm and presents + * only when challenged, wasting bandwidth. IE + * just carries on presenting until a new challenge + * occours. SimpleTest tries to follow the spirit of + * the original standards committee and treats the + * base URL as the root of a file tree shaped realm. + * @param SimpleUrl $url Base of realm. + * @param string $type Authentication type for this + * realm. Only Basic authentication + * is currently supported. + * @param string $realm Name of realm. + * @access public + */ + function addRealm($url, $type, $realm) { + $this->realms[$url->getHost()][$realm] = new SimpleRealm($type, $url); + } + + /** + * Sets the current identity to be presented + * against that realm. + * @param string $host Server hosting realm. + * @param string $realm Name of realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + function setIdentityForRealm($host, $realm, $username, $password) { + if (isset($this->realms[$host][$realm])) { + $this->realms[$host][$realm]->setIdentity($username, $password); + } + } + + /** + * Finds the name of the realm by comparing URLs. + * @param SimpleUrl $url URL to test. + * @return SimpleRealm Name of realm. + * @access private + */ + protected function findRealmFromUrl($url) { + if (! isset($this->realms[$url->getHost()])) { + return false; + } + foreach ($this->realms[$url->getHost()] as $name => $realm) { + if ($realm->isWithin($url)) { + return $realm; + } + } + return false; + } + + /** + * Presents the appropriate headers for this location. + * @param SimpleHttpRequest $request Request to modify. + * @param SimpleUrl $url Base of realm. + * @access public + */ + function addHeaders(&$request, $url) { + if ($url->getUsername() && $url->getPassword()) { + $username = $url->getUsername(); + $password = $url->getPassword(); + } elseif ($realm = $this->findRealmFromUrl($url)) { + $username = $realm->getUsername(); + $password = $realm->getPassword(); + } else { + return; + } + $this->addBasicHeaders($request, $username, $password); + } + + /** + * Presents the appropriate headers for this + * location for basic authentication. + * @param SimpleHttpRequest $request Request to modify. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + static function addBasicHeaders(&$request, $username, $password) { + if ($username && $password) { + $request->addHeaderLine( + 'Authorization: Basic ' . base64_encode("$username:$password")); + } + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/autorun.php b/lib/Swift/test-suite/lib/simpletest/autorun.php new file mode 100644 index 0000000..03729bf --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/autorun.php @@ -0,0 +1,97 @@ +createSuiteFromClasses( + basename(initial_file()), + $loader->selectRunnableTests($candidates)); + $result = $suite->run(new DefaultReporter()); + } catch (Exception $stack_frame_fix) { + print $stack_frame_fix->getMessage(); + $result = false; + } + if (SimpleReporter::inCli()) { + exit($result ? 0 : 1); + } +} + +/** + * Checks the current test context to see if a test has + * ever been run. + * @return boolean True if tests have run. + */ +function tests_have_run() { + if ($context = SimpleTest::getContext()) { + return (boolean)$context->getTest(); + } + return false; +} + +/** + * The first autorun file. + * @return string Filename of first autorun script. + */ +function initial_file() { + static $file = false; + if (! $file) { + if (isset($_SERVER, $_SERVER['SCRIPT_FILENAME'])) { + $file = $_SERVER['SCRIPT_FILENAME']; + } else { + $included_files = get_included_files(); + $file = reset($included_files); + } + } + return $file; +} + +/** + * Just the classes from the first autorun script. May + * get a few false positives, as it just does a regex based + * on following the word "class". + * @return array List of all possible classes in first + * autorun script. + */ +function classes_defined_in_initial_file() { + if (preg_match_all('/\bclass\s+(\w+)/i', file_get_contents(initial_file()), $matches)) { + return array_map('strtolower', $matches[1]); + } + return array(); +} + +/** + * Every class since the first autorun include. This + * is safe enough if require_once() is alwyas used. + * @return array Class names. + */ +function capture_new_classes() { + global $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES; + return array_map('strtolower', array_diff(get_declared_classes(), + $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES ? + $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES : array())); +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/browser.php b/lib/Swift/test-suite/lib/simpletest/browser.php new file mode 100644 index 0000000..da0379d --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/browser.php @@ -0,0 +1,1094 @@ +sequence = array(); + $this->position = -1; + } + + /** + * Test for no entries yet. + * @return boolean True if empty. + * @access private + */ + protected function isEmpty() { + return ($this->position == -1); + } + + /** + * Test for being at the beginning. + * @return boolean True if first. + * @access private + */ + protected function atBeginning() { + return ($this->position == 0) && ! $this->isEmpty(); + } + + /** + * Test for being at the last entry. + * @return boolean True if last. + * @access private + */ + protected function atEnd() { + return ($this->position + 1 >= count($this->sequence)) && ! $this->isEmpty(); + } + + /** + * Adds a successfully fetched page to the history. + * @param SimpleUrl $url URL of fetch. + * @param SimpleEncoding $parameters Any post data with the fetch. + * @access public + */ + function recordEntry($url, $parameters) { + $this->dropFuture(); + array_push( + $this->sequence, + array('url' => $url, 'parameters' => $parameters)); + $this->position++; + } + + /** + * Last fully qualified URL for current history + * position. + * @return SimpleUrl URL for this position. + * @access public + */ + function getUrl() { + if ($this->isEmpty()) { + return false; + } + return $this->sequence[$this->position]['url']; + } + + /** + * Parameters of last fetch from current history + * position. + * @return SimpleFormEncoding Post parameters. + * @access public + */ + function getParameters() { + if ($this->isEmpty()) { + return false; + } + return $this->sequence[$this->position]['parameters']; + } + + /** + * Step back one place in the history. Stops at + * the first page. + * @return boolean True if any previous entries. + * @access public + */ + function back() { + if ($this->isEmpty() || $this->atBeginning()) { + return false; + } + $this->position--; + return true; + } + + /** + * Step forward one place. If already at the + * latest entry then nothing will happen. + * @return boolean True if any future entries. + * @access public + */ + function forward() { + if ($this->isEmpty() || $this->atEnd()) { + return false; + } + $this->position++; + return true; + } + + /** + * Ditches all future entries beyond the current + * point. + * @access private + */ + protected function dropFuture() { + if ($this->isEmpty()) { + return; + } + while (! $this->atEnd()) { + array_pop($this->sequence); + } + } +} + +/** + * Simulated web browser. This is an aggregate of + * the user agent, the HTML parsing, request history + * and the last header set. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleBrowser { + private $user_agent; + private $page; + private $history; + private $ignore_frames; + private $maximum_nested_frames; + + /** + * Starts with a fresh browser with no + * cookie or any other state information. The + * exception is that a default proxy will be + * set up if specified in the options. + * @access public + */ + function __construct() { + $this->user_agent = $this->createUserAgent(); + $this->user_agent->useProxy( + SimpleTest::getDefaultProxy(), + SimpleTest::getDefaultProxyUsername(), + SimpleTest::getDefaultProxyPassword()); + $this->page = new SimplePage(); + $this->history = $this->createHistory(); + $this->ignore_frames = false; + $this->maximum_nested_frames = DEFAULT_MAX_NESTED_FRAMES; + } + + /** + * Creates the underlying user agent. + * @return SimpleFetcher Content fetcher. + * @access protected + */ + protected function createUserAgent() { + return new SimpleUserAgent(); + } + + /** + * Creates a new empty history list. + * @return SimpleBrowserHistory New list. + * @access protected + */ + protected function createHistory() { + return new SimpleBrowserHistory(); + } + + /** + * Disables frames support. Frames will not be fetched + * and the frameset page will be used instead. + * @access public + */ + function ignoreFrames() { + $this->ignore_frames = true; + } + + /** + * Enables frames support. Frames will be fetched from + * now on. + * @access public + */ + function useFrames() { + $this->ignore_frames = false; + } + + /** + * Switches off cookie sending and recieving. + * @access public + */ + function ignoreCookies() { + $this->user_agent->ignoreCookies(); + } + + /** + * Switches back on the cookie sending and recieving. + * @access public + */ + function useCookies() { + $this->user_agent->useCookies(); + } + + /** + * Parses the raw content into a page. Will load further + * frame pages unless frames are disabled. + * @param SimpleHttpResponse $response Response from fetch. + * @param integer $depth Nested frameset depth. + * @return SimplePage Parsed HTML. + * @access private + */ + protected function parse($response, $depth = 0) { + $page = $this->buildPage($response); + if ($this->ignore_frames || ! $page->hasFrames() || ($depth > $this->maximum_nested_frames)) { + return $page; + } + $frameset = new SimpleFrameset($page); + foreach ($page->getFrameset() as $key => $url) { + $frame = $this->fetch($url, new SimpleGetEncoding(), $depth + 1); + $frameset->addFrame($frame, $key); + } + return $frameset; + } + + /** + * Assembles the parsing machinery and actually parses + * a single page. Frees all of the builder memory and so + * unjams the PHP memory management. + * @param SimpleHttpResponse $response Response from fetch. + * @return SimplePage Parsed top level page. + * @access protected + */ + protected function buildPage($response) { + $builder = new SimplePageBuilder(); + $page = $builder->parse($response); + $builder->free(); + unset($builder); + return $page; + } + + /** + * Fetches a page. Jointly recursive with the parse() + * method as it descends a frameset. + * @param string/SimpleUrl $url Target to fetch. + * @param SimpleEncoding $encoding GET/POST parameters. + * @param integer $depth Nested frameset depth protection. + * @return SimplePage Parsed page. + * @access private + */ + protected function fetch($url, $encoding, $depth = 0) { + $response = $this->user_agent->fetchResponse($url, $encoding); + if ($response->isError()) { + return new SimplePage($response); + } + return $this->parse($response, $depth); + } + + /** + * Fetches a page or a single frame if that is the current + * focus. + * @param SimpleUrl $url Target to fetch. + * @param SimpleEncoding $parameters GET/POST parameters. + * @return string Raw content of page. + * @access private + */ + protected function load($url, $parameters) { + $frame = $url->getTarget(); + if (! $frame || ! $this->page->hasFrames() || (strtolower($frame) == '_top')) { + return $this->loadPage($url, $parameters); + } + return $this->loadFrame(array($frame), $url, $parameters); + } + + /** + * Fetches a page and makes it the current page/frame. + * @param string/SimpleUrl $url Target to fetch as string. + * @param SimplePostEncoding $parameters POST parameters. + * @return string Raw content of page. + * @access private + */ + protected function loadPage($url, $parameters) { + $this->page = $this->fetch($url, $parameters); + $this->history->recordEntry( + $this->page->getUrl(), + $this->page->getRequestData()); + return $this->page->getRaw(); + } + + /** + * Fetches a frame into the existing frameset replacing the + * original. + * @param array $frames List of names to drill down. + * @param string/SimpleUrl $url Target to fetch as string. + * @param SimpleFormEncoding $parameters POST parameters. + * @return string Raw content of page. + * @access private + */ + protected function loadFrame($frames, $url, $parameters) { + $page = $this->fetch($url, $parameters); + $this->page->setFrame($frames, $page); + return $page->getRaw(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $date Time when session restarted. + * If omitted then all persistent + * cookies are kept. + * @access public + */ + function restart($date = false) { + $this->user_agent->restart($date); + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->user_agent->addHeader($header); + } + + /** + * Ages the cookies by the specified time. + * @param integer $interval Amount in seconds. + * @access public + */ + function ageCookies($interval) { + $this->user_agent->ageCookies($interval); + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $this->user_agent->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + return $this->user_agent->getCookieValue($host, $path, $name); + } + + /** + * Reads the current cookies for the current URL. + * @param string $name Key of cookie to find. + * @return string Null if there is no current URL, false + * if the cookie is not set. + * @access public + */ + function getCurrentCookieValue($name) { + return $this->user_agent->getBaseCookieValue($name, $this->page->getUrl()); + } + + /** + * Sets the maximum number of redirects before + * a page will be loaded anyway. + * @param integer $max Most hops allowed. + * @access public + */ + function setMaximumRedirects($max) { + $this->user_agent->setMaximumRedirects($max); + } + + /** + * Sets the maximum number of nesting of framed pages + * within a framed page to prevent loops. + * @param integer $max Highest depth allowed. + * @access public + */ + function setMaximumNestedFrames($max) { + $this->maximum_nested_frames = $max; + } + + /** + * Sets the socket timeout for opening a connection. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->user_agent->setConnectionTimeout($timeout); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username = false, $password = false) { + $this->user_agent->useProxy($proxy, $username, $password); + } + + /** + * Fetches the page content with a HEAD request. + * Will affect cookies, but will not change the base URL. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash/SimpleHeadEncoding $parameters Additional parameters for + * HEAD request. + * @return boolean True if successful. + * @access public + */ + function head($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + $response = &$this->user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters)); + return ! $response->isError(); + } + + /** + * Fetches the page content with a simple GET request. + * @param string/SimpleUrl $url Target to fetch. + * @param hash/SimpleFormEncoding $parameters Additional parameters for + * GET request. + * @return string Content of page or false. + * @access public + */ + function get($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + return $this->load($url, new SimpleGetEncoding($parameters)); + } + + /** + * Fetches the page content with a POST request. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash/SimpleFormEncoding $parameters POST parameters. + * @return string Content of page. + * @access public + */ + function post($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + return $this->load($url, new SimplePostEncoding($parameters)); + } + + /** + * Equivalent to hitting the retry button on the + * browser. Will attempt to repeat the page fetch. If + * there is no history to repeat it will give false. + * @return string/boolean Content if fetch succeeded + * else false. + * @access public + */ + function retry() { + $frames = $this->page->getFrameFocus(); + if (count($frames) > 0) { + $this->loadFrame( + $frames, + $this->page->getUrl(), + $this->page->getRequestData()); + return $this->page->getRaw(); + } + if ($url = $this->history->getUrl()) { + $this->page = $this->fetch($url, $this->history->getParameters()); + return $this->page->getRaw(); + } + return false; + } + + /** + * Equivalent to hitting the back button on the + * browser. The browser history is unchanged on + * failure. The page content is refetched as there + * is no concept of content caching in SimpleTest. + * @return boolean True if history entry and + * fetch succeeded + * @access public + */ + function back() { + if (! $this->history->back()) { + return false; + } + $content = $this->retry(); + if (! $content) { + $this->history->forward(); + } + return $content; + } + + /** + * Equivalent to hitting the forward button on the + * browser. The browser history is unchanged on + * failure. The page content is refetched as there + * is no concept of content caching in SimpleTest. + * @return boolean True if history entry and + * fetch succeeded + * @access public + */ + function forward() { + if (! $this->history->forward()) { + return false; + } + $content = $this->retry(); + if (! $content) { + $this->history->back(); + } + return $content; + } + + /** + * Retries a request after setting the authentication + * for the current realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @return boolean True if successful fetch. Note + * that authentication may still have + * failed. + * @access public + */ + function authenticate($username, $password) { + if (! $this->page->getRealm()) { + return false; + } + $url = $this->page->getUrl(); + if (! $url) { + return false; + } + $this->user_agent->setIdentity( + $url->getHost(), + $this->page->getRealm(), + $username, + $password); + return $this->retry(); + } + + /** + * Accessor for a breakdown of the frameset. + * @return array Hash tree of frames by name + * or index if no name. + * @access public + */ + function getFrames() { + return $this->page->getFrames(); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. + * @return integer/string/boolean Label if any, otherwise + * the position in the frameset + * or false if none. + * @access public + */ + function getFrameFocus() { + return $this->page->getFrameFocus(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + return $this->page->setFrameFocusByIndex($choice); + } + + /** + * Sets the focus by name. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + return $this->page->setFrameFocus($name); + } + + /** + * Clears the frame focus. All frames will be searched + * for content. + * @access public + */ + function clearFrameFocus() { + return $this->page->clearFrameFocus(); + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + return $this->page->getTransportError(); + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + return $this->page->getMimeType(); + } + + /** + * Accessor for last response code. + * @return integer Last HTTP response code received. + * @access public + */ + function getResponseCode() { + return $this->page->getResponseCode(); + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + return $this->page->getAuthentication(); + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + return $this->page->getRealm(); + } + + /** + * Accessor for current URL of page or frame if + * focused. + * @return string Location of current page or frame as + * a string. + */ + function getUrl() { + $url = $this->page->getUrl(); + return $url ? $url->asString() : false; + } + + /** + * Accessor for base URL of page if set via BASE tag + * @return string base URL + */ + function getBaseUrl() { + $url = $this->page->getBaseUrl(); + return $url ? $url->asString() : false; + } + + /** + * Accessor for raw bytes sent down the wire. + * @return string Original text sent. + * @access public + */ + function getRequest() { + return $this->page->getRequest(); + } + + /** + * Accessor for raw header information. + * @return string Header block. + * @access public + */ + function getHeaders() { + return $this->page->getHeaders(); + } + + /** + * Accessor for raw page information. + * @return string Original text content of web page. + * @access public + */ + function getContent() { + return $this->page->getRaw(); + } + + /** + * Accessor for plain text version of the page. + * @return string Normalised text representation. + * @access public + */ + function getContentAsText() { + return $this->page->getText(); + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + return $this->page->getTitle(); + } + + /** + * Accessor for a list of all links in current page. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getUrls() { + return $this->page->getUrls(); + } + + /** + * Sets all form fields with that name. + * @param string $label Name or label of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setField($label, $value, $position=false) { + return $this->page->setField(new SimpleByLabelOrName($label), $value, $position); + } + + /** + * Sets all form fields with that name. Will use label if + * one is available (not yet implemented). + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldByName($name, $value, $position=false) { + return $this->page->setField(new SimpleByName($name), $value, $position); + } + + /** + * Sets all form fields with that id attribute. + * @param string/integer $id Id of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldById($id, $value) { + return $this->page->setField(new SimpleById($id), $value); + } + + /** + * Accessor for a form element value within the page. + * Finds the first match. + * @param string $label Field label. + * @return string/boolean A value if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($label) { + return $this->page->getField(new SimpleByLabelOrName($label)); + } + + /** + * Accessor for a form element value within the page. + * Finds the first match. + * @param string $name Field name. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getFieldByName($name) { + return $this->page->getField(new SimpleByName($name)); + } + + /** + * Accessor for a form element value within the page. + * @param string/integer $id Id of field in forms. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getFieldById($id) { + return $this->page->getField(new SimpleById($id)); + } + + /** + * Clicks the submit button by label. The owning + * form will be submitted by this. + * @param string $label Button label. An unlabeled + * button can be triggered by 'Submit'. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickSubmit($label = 'Submit', $additional = false) { + if (! ($form = $this->page->getFormBySubmit(new SimpleByLabel($label)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitButton(new SimpleByLabel($label), $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit button by name attribute. The owning + * form will be submitted by this. + * @param string $name Button name. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickSubmitByName($name, $additional = false) { + if (! ($form = $this->page->getFormBySubmit(new SimpleByName($name)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitButton(new SimpleByName($name), $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit button by ID attribute of the button + * itself. The owning form will be submitted by this. + * @param string $id Button ID. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickSubmitById($id, $additional = false) { + if (! ($form = $this->page->getFormBySubmit(new SimpleById($id)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitButton(new SimpleById($id), $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Tests to see if a submit button exists with this + * label. + * @param string $label Button label. + * @return boolean True if present. + * @access public + */ + function isSubmit($label) { + return (boolean)$this->page->getFormBySubmit(new SimpleByLabel($label)); + } + + /** + * Clicks the submit image by some kind of label. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $label ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickImage($label, $x = 1, $y = 1, $additional = false) { + if (! ($form = $this->page->getFormByImage(new SimpleByLabel($label)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitImage(new SimpleByLabel($label), $x, $y, $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit image by the name. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $name Name attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickImageByName($name, $x = 1, $y = 1, $additional = false) { + if (! ($form = $this->page->getFormByImage(new SimpleByName($name)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitImage(new SimpleByName($name), $x, $y, $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit image by ID attribute. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param integer/string $id ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickImageById($id, $x = 1, $y = 1, $additional = false) { + if (! ($form = $this->page->getFormByImage(new SimpleById($id)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitImage(new SimpleById($id), $x, $y, $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Tests to see if an image exists with this + * title or alt text. + * @param string $label Image text. + * @return boolean True if present. + * @access public + */ + function isImage($label) { + return (boolean)$this->page->getFormByImage(new SimpleByLabel($label)); + } + + /** + * Submits a form by the ID. + * @param string $id The form ID. No submit button value + * will be sent. + * @return string/boolean Page on success. + * @access public + */ + function submitFormById($id) { + if (! ($form = $this->page->getFormById($id))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submit()); + return ($success ? $this->getContent() : $success); + } + + /** + * Finds a URL by label. Will find the first link + * found with this link text by default, or a later + * one if an index is given. The match ignores case and + * white space issues. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return string/boolean URL on success. + * @access public + */ + function getLink($label, $index = 0) { + $urls = $this->page->getUrlsByLabel($label); + if (count($urls) == 0) { + return false; + } + if (count($urls) < $index + 1) { + return false; + } + return $urls[$index]; + } + + /** + * Follows a link by label. Will click the first link + * found with this link text by default, or a later + * one if an index is given. The match ignores case and + * white space issues. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return string/boolean Page on success. + * @access public + */ + function clickLink($label, $index = 0) { + $url = $this->getLink($label, $index); + if ($url === false) { + return false; + } + $this->load($url, new SimpleGetEncoding()); + return $this->getContent(); + } + + /** + * Finds a link by id attribute. + * @param string $id ID attribute value. + * @return string/boolean URL on success. + * @access public + */ + function getLinkById($id) { + return $this->page->getUrlById($id); + } + + /** + * Follows a link by id attribute. + * @param string $id ID attribute value. + * @return string/boolean Page on success. + * @access public + */ + function clickLinkById($id) { + if (! ($url = $this->getLinkById($id))) { + return false; + } + $this->load($url, new SimpleGetEncoding()); + return $this->getContent(); + } + + /** + * Clicks a visible text item. Will first try buttons, + * then links and then images. + * @param string $label Visible text or alt text. + * @return string/boolean Raw page or false. + * @access public + */ + function click($label) { + $raw = $this->clickSubmit($label); + if (! $raw) { + $raw = $this->clickLink($label); + } + if (! $raw) { + $raw = $this->clickImage($label); + } + return $raw; + } + + /** + * Tests to see if a click target exists. + * @param string $label Visible text or alt text. + * @return boolean True if target present. + * @access public + */ + function isClickable($label) { + return $this->isSubmit($label) || ($this->getLink($label) !== false) || $this->isImage($label); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/collector.php b/lib/Swift/test-suite/lib/simpletest/collector.php new file mode 100644 index 0000000..0f8e91f --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/collector.php @@ -0,0 +1,122 @@ + + * @package SimpleTest + * @subpackage UnitTester + * @version $Id: collector.php 1784 2008-04-26 13:07:14Z pp11 $ + */ + +/** + * The basic collector for {@link GroupTest} + * + * @see collect(), GroupTest::collect() + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleCollector { + + /** + * Strips off any kind of slash at the end so as to normalise the path. + * @param string $path Path to normalise. + * @return string Path without trailing slash. + */ + protected function removeTrailingSlash($path) { + if (substr($path, -1) == DIRECTORY_SEPARATOR) { + return substr($path, 0, -1); + } elseif (substr($path, -1) == '/') { + return substr($path, 0, -1); + } else { + return $path; + } + } + + /** + * Scans the directory and adds what it can. + * @param object $test Group test with {@link GroupTest::addTestFile()} method. + * @param string $path Directory to scan. + * @see _attemptToAdd() + */ + function collect(&$test, $path) { + $path = $this->removeTrailingSlash($path); + if ($handle = opendir($path)) { + while (($entry = readdir($handle)) !== false) { + if ($this->isHidden($entry)) { + continue; + } + $this->handle($test, $path . DIRECTORY_SEPARATOR . $entry); + } + closedir($handle); + } + } + + /** + * This method determines what should be done with a given file and adds + * it via {@link GroupTest::addTestFile()} if necessary. + * + * This method should be overriden to provide custom matching criteria, + * such as pattern matching, recursive matching, etc. For an example, see + * {@link SimplePatternCollector::_handle()}. + * + * @param object $test Group test with {@link GroupTest::addTestFile()} method. + * @param string $filename A filename as generated by {@link collect()} + * @see collect() + * @access protected + */ + protected function handle(&$test, $file) { + if (is_dir($file)) { + return; + } + $test->addFile($file); + } + + /** + * Tests for hidden files so as to skip them. Currently + * only tests for Unix hidden files. + * @param string $filename Plain filename. + * @return boolean True if hidden file. + * @access private + */ + protected function isHidden($filename) { + return strncmp($filename, '.', 1) == 0; + } +} + +/** + * An extension to {@link SimpleCollector} that only adds files matching a + * given pattern. + * + * @package SimpleTest + * @subpackage UnitTester + * @see SimpleCollector + */ +class SimplePatternCollector extends SimpleCollector { + private $pattern; + + /** + * + * @param string $pattern Perl compatible regex to test name against + * See {@link http://us4.php.net/manual/en/reference.pcre.pattern.syntax.php PHP's PCRE} + * for full documentation of valid pattern.s + */ + function __construct($pattern = '/php$/i') { + $this->pattern = $pattern; + } + + /** + * Attempts to add files that match a given pattern. + * + * @see SimpleCollector::_handle() + * @param object $test Group test with {@link GroupTest::addTestFile()} method. + * @param string $path Directory to scan. + * @access protected + */ + protected function handle(&$test, $filename) { + if (preg_match($this->pattern, $filename)) { + parent::handle($test, $filename); + } + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/compatibility.php b/lib/Swift/test-suite/lib/simpletest/compatibility.php new file mode 100644 index 0000000..6e90fe4 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/compatibility.php @@ -0,0 +1,166 @@ += 0) { + eval('$copy = clone $object;'); + return $copy; + } + return $object; + } + + /** + * Identity test. Drops back to equality + types for PHP5 + * objects as the === operator counts as the + * stronger reference constraint. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if identical. + * @access public + */ + static function isIdentical($first, $second) { + if (version_compare(phpversion(), '5') >= 0) { + return SimpleTestCompatibility::isIdenticalType($first, $second); + } + if ($first != $second) { + return false; + } + return ($first === $second); + } + + /** + * Recursive type test. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if same type. + * @access private + */ + protected static function isIdenticalType($first, $second) { + if (gettype($first) != gettype($second)) { + return false; + } + if (is_object($first) && is_object($second)) { + if (get_class($first) != get_class($second)) { + return false; + } + return SimpleTestCompatibility::isArrayOfIdenticalTypes( + get_object_vars($first), + get_object_vars($second)); + } + if (is_array($first) && is_array($second)) { + return SimpleTestCompatibility::isArrayOfIdenticalTypes($first, $second); + } + if ($first !== $second) { + return false; + } + return true; + } + + /** + * Recursive type test for each element of an array. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if identical. + * @access private + */ + protected static function isArrayOfIdenticalTypes($first, $second) { + if (array_keys($first) != array_keys($second)) { + return false; + } + foreach (array_keys($first) as $key) { + $is_identical = SimpleTestCompatibility::isIdenticalType( + $first[$key], + $second[$key]); + if (! $is_identical) { + return false; + } + } + return true; + } + + /** + * Test for two variables being aliases. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if same. + * @access public + */ + static function isReference(&$first, &$second) { + if (version_compare(phpversion(), '5', '>=') && is_object($first)) { + return ($first === $second); + } + if (is_object($first) && is_object($second)) { + $id = uniqid("test"); + $first->$id = true; + $is_ref = isset($second->$id); + unset($first->$id); + return $is_ref; + } + $temp = $first; + $first = uniqid("test"); + $is_ref = ($first === $second); + $first = $temp; + return $is_ref; + } + + /** + * Test to see if an object is a member of a + * class hiearchy. + * @param object $object Object to test. + * @param string $class Root name of hiearchy. + * @return boolean True if class in hiearchy. + * @access public + */ + static function isA($object, $class) { + if (version_compare(phpversion(), '5') >= 0) { + if (! class_exists($class, false)) { + if (function_exists('interface_exists')) { + if (! interface_exists($class, false)) { + return false; + } + } + } + eval("\$is_a = \$object instanceof $class;"); + return $is_a; + } + if (function_exists('is_a')) { + return is_a($object, $class); + } + return ((strtolower($class) == get_class($object)) + or (is_subclass_of($object, $class))); + } + + /** + * Sets a socket timeout for each chunk. + * @param resource $handle Socket handle. + * @param integer $timeout Limit in seconds. + * @access public + */ + static function setTimeout($handle, $timeout) { + if (function_exists('stream_set_timeout')) { + stream_set_timeout($handle, $timeout, 0); + } elseif (function_exists('socket_set_timeout')) { + socket_set_timeout($handle, $timeout, 0); + } elseif (function_exists('set_socket_timeout')) { + set_socket_timeout($handle, $timeout, 0); + } + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/cookies.php b/lib/Swift/test-suite/lib/simpletest/cookies.php new file mode 100644 index 0000000..675bd20 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/cookies.php @@ -0,0 +1,380 @@ +host = false; + $this->name = $name; + $this->value = $value; + $this->path = ($path ? $this->fixPath($path) : "/"); + $this->expiry = false; + if (is_string($expiry)) { + $this->expiry = strtotime($expiry); + } elseif (is_integer($expiry)) { + $this->expiry = $expiry; + } + $this->is_secure = $is_secure; + } + + /** + * Sets the host. The cookie rules determine + * that the first two parts are taken for + * certain TLDs and three for others. If the + * new host does not match these rules then the + * call will fail. + * @param string $host New hostname. + * @return boolean True if hostname is valid. + * @access public + */ + function setHost($host) { + if ($host = $this->truncateHost($host)) { + $this->host = $host; + return true; + } + return false; + } + + /** + * Accessor for the truncated host to which this + * cookie applies. + * @return string Truncated hostname. + * @access public + */ + function getHost() { + return $this->host; + } + + /** + * Test for a cookie being valid for a host name. + * @param string $host Host to test against. + * @return boolean True if the cookie would be valid + * here. + */ + function isValidHost($host) { + return ($this->truncateHost($host) === $this->getHost()); + } + + /** + * Extracts just the domain part that determines a + * cookie's host validity. + * @param string $host Host name to truncate. + * @return string Domain or false on a bad host. + * @access private + */ + protected function truncateHost($host) { + $tlds = SimpleUrl::getAllTopLevelDomains(); + if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) { + return $matches[0]; + } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) { + return $matches[0]; + } + return false; + } + + /** + * Accessor for name. + * @return string Cookie key. + * @access public + */ + function getName() { + return $this->name; + } + + /** + * Accessor for value. A deleted cookie will + * have an empty string for this. + * @return string Cookie value. + * @access public + */ + function getValue() { + return $this->value; + } + + /** + * Accessor for path. + * @return string Valid cookie path. + * @access public + */ + function getPath() { + return $this->path; + } + + /** + * Tests a path to see if the cookie applies + * there. The test path must be longer or + * equal to the cookie path. + * @param string $path Path to test against. + * @return boolean True if cookie valid here. + * @access public + */ + function isValidPath($path) { + return (strncmp( + $this->fixPath($path), + $this->getPath(), + strlen($this->getPath())) == 0); + } + + /** + * Accessor for expiry. + * @return string Expiry string. + * @access public + */ + function getExpiry() { + if (! $this->expiry) { + return false; + } + return gmdate("D, d M Y H:i:s", $this->expiry) . " GMT"; + } + + /** + * Test to see if cookie is expired against + * the cookie format time or timestamp. + * Will give true for a session cookie. + * @param integer/string $now Time to test against. Result + * will be false if this time + * is later than the cookie expiry. + * Can be either a timestamp integer + * or a cookie format date. + * @access public + */ + function isExpired($now) { + if (! $this->expiry) { + return true; + } + if (is_string($now)) { + $now = strtotime($now); + } + return ($this->expiry < $now); + } + + /** + * Ages the cookie by the specified number of + * seconds. + * @param integer $interval In seconds. + * @public + */ + function agePrematurely($interval) { + if ($this->expiry) { + $this->expiry -= $interval; + } + } + + /** + * Accessor for the secure flag. + * @return boolean True if cookie needs SSL. + * @access public + */ + function isSecure() { + return $this->is_secure; + } + + /** + * Adds a trailing and leading slash to the path + * if missing. + * @param string $path Path to fix. + * @access private + */ + protected function fixPath($path) { + if (substr($path, 0, 1) != '/') { + $path = '/' . $path; + } + if (substr($path, -1, 1) != '/') { + $path .= '/'; + } + return $path; + } +} + +/** + * Repository for cookies. This stuff is a + * tiny bit browser dependent. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCookieJar { + private $cookies; + + /** + * Constructor. Jar starts empty. + * @access public + */ + function __construct() { + $this->cookies = array(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $now Time to test expiry against. + * @access public + */ + function restartSession($date = false) { + $surviving_cookies = array(); + for ($i = 0; $i < count($this->cookies); $i++) { + if (! $this->cookies[$i]->getValue()) { + continue; + } + if (! $this->cookies[$i]->getExpiry()) { + continue; + } + if ($date && $this->cookies[$i]->isExpired($date)) { + continue; + } + $surviving_cookies[] = $this->cookies[$i]; + } + $this->cookies = $surviving_cookies; + } + + /** + * Ages all cookies in the cookie jar. + * @param integer $interval The old session is moved + * into the past by this number + * of seconds. Cookies now over + * age will be removed. + * @access public + */ + function agePrematurely($interval) { + for ($i = 0; $i < count($this->cookies); $i++) { + $this->cookies[$i]->agePrematurely($interval); + } + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $cookie = new SimpleCookie($name, $value, $path, $expiry); + if ($host) { + $cookie->setHost($host); + } + $this->cookies[$this->findFirstMatch($cookie)] = $cookie; + } + + /** + * Finds a matching cookie to write over or the + * first empty slot if none. + * @param SimpleCookie $cookie Cookie to write into jar. + * @return integer Available slot. + * @access private + */ + protected function findFirstMatch($cookie) { + for ($i = 0; $i < count($this->cookies); $i++) { + $is_match = $this->isMatch( + $cookie, + $this->cookies[$i]->getHost(), + $this->cookies[$i]->getPath(), + $this->cookies[$i]->getName()); + if ($is_match) { + return $i; + } + } + return count($this->cookies); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. Looks for the longest path that + * matches. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + $longest_path = ''; + foreach ($this->cookies as $cookie) { + if ($this->isMatch($cookie, $host, $path, $name)) { + if (strlen($cookie->getPath()) > strlen($longest_path)) { + $value = $cookie->getValue(); + $longest_path = $cookie->getPath(); + } + } + } + return (isset($value) ? $value : false); + } + + /** + * Tests cookie for matching against search + * criteria. + * @param SimpleTest $cookie Cookie to test. + * @param string $host Host must match. + * @param string $path Cookie path must be shorter than + * this path. + * @param string $name Name must match. + * @return boolean True if matched. + * @access private + */ + protected function isMatch($cookie, $host, $path, $name) { + if ($cookie->getName() != $name) { + return false; + } + if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) { + return false; + } + if (! $cookie->isValidPath($path)) { + return false; + } + return true; + } + + /** + * Uses a URL to sift relevant cookies by host and + * path. Results are list of strings of form "name=value". + * @param SimpleUrl $url Url to select by. + * @return array Valid name and value pairs. + * @access public + */ + function selectAsPairs($url) { + $pairs = array(); + foreach ($this->cookies as $cookie) { + if ($this->isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) { + $pairs[] = $cookie->getName() . '=' . $cookie->getValue(); + } + } + return $pairs; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/default_reporter.php b/lib/Swift/test-suite/lib/simpletest/default_reporter.php new file mode 100644 index 0000000..9ec708d --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/default_reporter.php @@ -0,0 +1,163 @@ + 'case', 'c' => 'case', + 'test' => 'test', 't' => 'test', + ); + private $case = ''; + private $test = ''; + private $xml = false; + private $help = false; + private $no_skips = false; + + /** + * Parses raw command line arguments into object properties. + * @param string $arguments Raw commend line arguments. + */ + function __construct($arguments) { + if (! is_array($arguments)) { + return; + } + foreach ($arguments as $i => $argument) { + if (preg_match('/^--?(test|case|t|c)=(.+)$/', $argument, $matches)) { + $property = $this->to_property[$matches[1]]; + $this->$property = $matches[2]; + } elseif (preg_match('/^--?(test|case|t|c)$/', $argument, $matches)) { + $property = $this->to_property[$matches[1]]; + if (isset($arguments[$i + 1])) { + $this->$property = $arguments[$i + 1]; + } + } elseif (preg_match('/^--?(xml|x)$/', $argument)) { + $this->xml = true; + } elseif (preg_match('/^--?(no-skip|no-skips|s)$/', $argument)) { + $this->no_skips = true; + } elseif (preg_match('/^--?(help|h)$/', $argument)) { + $this->help = true; + } + } + } + + /** + * Run only this test. + * @return string Test name to run. + */ + function getTest() { + return $this->test; + } + + /** + * Run only this test suite. + * @return string Test class name to run. + */ + function getTestCase() { + return $this->case; + } + + /** + * Output should be XML or not. + * @return boolean True if XML desired. + */ + function isXml() { + return $this->xml; + } + + /** + * Output should suppress skip messages. + * @return boolean True for no skips. + */ + function noSkips() { + return $this->no_skips; + } + + /** + * Output should be a help message. Disabled during XML mode. + * @return boolean True if help message desired. + */ + function help() { + return $this->help && !$this->xml; + } + + /** + * Returns plain-text help message for command line runner. + * @return string String help message + */ + function getHelpText() { + return << [args...] + + -c Run only the test-case + -t Run only the test method + -s Suppress skip messages + -x Return test results in XML + -h Display this help message + +HELP; + } + +} + +/** + * The default reporter used by SimpleTest's autorun + * feature. The actual reporters used are dependency + * injected and can be overridden. + * @package SimpleTest + * @subpackage UnitTester + */ +class DefaultReporter extends SimpleReporterDecorator { + + /** + * Assembles the appopriate reporter for the environment. + */ + function __construct() { + if (SimpleReporter::inCli()) { + $parser = new SimpleCommandLineParser($_SERVER['argv']); + $interfaces = $parser->isXml() ? array('XmlReporter') : array('TextReporter'); + if ($parser->help()) { + // I'm not sure if we should do the echo'ing here -- ezyang + echo $parser->getHelpText(); + exit(1); + } + $reporter = new SelectiveReporter( + SimpleTest::preferred($interfaces), + $parser->getTestCase(), + $parser->getTest()); + if ($parser->noSkips()) { + $reporter = new NoSkipsReporter($reporter); + } + } else { + $reporter = new SelectiveReporter( + SimpleTest::preferred('HtmlReporter'), + @$_GET['c'], + @$_GET['t']); + if (@$_GET['skips'] == 'no' || @$_GET['show-skips'] == 'no') { + $reporter = new NoSkipsReporter($reporter); + } + } + parent::__construct($reporter); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/detached.php b/lib/Swift/test-suite/lib/simpletest/detached.php new file mode 100644 index 0000000..a325e14 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/detached.php @@ -0,0 +1,96 @@ +command = $command; + $this->dry_command = $dry_command ? $dry_command : $command; + $this->size = false; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->command; + } + + /** + * Runs the top level test for this class. Currently + * reads the data as a single chunk. I'll fix this + * once I have added iteration to the browser. + * @param SimpleReporter $reporter Target of test results. + * @returns boolean True if no failures. + * @access public + */ + function run(&$reporter) { + $shell = &new SimpleShell(); + $shell->execute($this->command); + $parser = &$this->createParser($reporter); + if (! $parser->parse($shell->getOutput())) { + trigger_error('Cannot parse incoming XML from [' . $this->command . ']'); + return false; + } + return true; + } + + /** + * Accessor for the number of subtests. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + if ($this->size === false) { + $shell = &new SimpleShell(); + $shell->execute($this->dry_command); + $reporter = &new SimpleReporter(); + $parser = &$this->createParser($reporter); + if (! $parser->parse($shell->getOutput())) { + trigger_error('Cannot parse incoming XML from [' . $this->dry_command . ']'); + return false; + } + $this->size = $reporter->getTestCaseCount(); + } + return $this->size; + } + + /** + * Creates the XML parser. + * @param SimpleReporter $reporter Target of test results. + * @return SimpleTestXmlListener XML reader. + * @access protected + */ + protected function &createParser(&$reporter) { + return new SimpleTestXmlParser($reporter); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/dumper.php b/lib/Swift/test-suite/lib/simpletest/dumper.php new file mode 100644 index 0000000..ef2662d --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/dumper.php @@ -0,0 +1,359 @@ +getType($value); + switch($type) { + case "Null": + return "NULL"; + case "Boolean": + return "Boolean: " . ($value ? "true" : "false"); + case "Array": + return "Array: " . count($value) . " items"; + case "Object": + return "Object: of " . get_class($value); + case "String": + return "String: " . $this->clipString($value, 200); + default: + return "$type: $value"; + } + return "Unknown"; + } + + /** + * Gets the string representation of a type. + * @param mixed $value Variable to check against. + * @return string Type. + * @access public + */ + function getType($value) { + if (! isset($value)) { + return "Null"; + } elseif (is_bool($value)) { + return "Boolean"; + } elseif (is_string($value)) { + return "String"; + } elseif (is_integer($value)) { + return "Integer"; + } elseif (is_float($value)) { + return "Float"; + } elseif (is_array($value)) { + return "Array"; + } elseif (is_resource($value)) { + return "Resource"; + } elseif (is_object($value)) { + return "Object"; + } + return "Unknown"; + } + + /** + * Creates a human readable description of the + * difference between two variables. Uses a + * dynamic call. + * @param mixed $first First variable. + * @param mixed $second Value to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Description of difference. + * @access public + */ + function describeDifference($first, $second, $identical = false) { + if ($identical) { + if (! $this->isTypeMatch($first, $second)) { + return "with type mismatch as [" . $this->describeValue($first) . + "] does not match [" . $this->describeValue($second) . "]"; + } + } + $type = $this->getType($first); + if ($type == "Unknown") { + return "with unknown type"; + } + $method = 'describe' . $type . 'Difference'; + return $this->$method($first, $second, $identical); + } + + /** + * Tests to see if types match. + * @param mixed $first First variable. + * @param mixed $second Value to compare with. + * @return boolean True if matches. + * @access private + */ + protected function isTypeMatch($first, $second) { + return ($this->getType($first) == $this->getType($second)); + } + + /** + * Clips a string to a maximum length. + * @param string $value String to truncate. + * @param integer $size Minimum string size to show. + * @param integer $position Centre of string section. + * @return string Shortened version. + * @access public + */ + function clipString($value, $size, $position = 0) { + $length = strlen($value); + if ($length <= $size) { + return $value; + } + $position = min($position, $length); + $start = ($size/2 > $position ? 0 : $position - $size/2); + if ($start + $size > $length) { + $start = $length - $size; + } + $value = substr($value, $start, $size); + return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : ""); + } + + /** + * Creates a human readable description of the + * difference between two variables. The minimal + * version. + * @param null $first First value. + * @param mixed $second Value to compare with. + * @return string Human readable description. + * @access private + */ + protected function describeGenericDifference($first, $second) { + return "as [" . $this->describeValue($first) . + "] does not match [" . + $this->describeValue($second) . "]"; + } + + /** + * Creates a human readable description of the + * difference between a null and another variable. + * @param null $first First null. + * @param mixed $second Null to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeNullDifference($first, $second, $identical) { + return $this->describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between a boolean and another variable. + * @param boolean $first First boolean. + * @param mixed $second Boolean to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeBooleanDifference($first, $second, $identical) { + return $this->describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between a string and another variable. + * @param string $first First string. + * @param mixed $second String to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeStringDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + $position = $this->stringDiffersAt($first, $second); + $message = "at character $position"; + $message .= " with [" . + $this->clipString($first, 200, $position) . "] and [" . + $this->clipString($second, 200, $position) . "]"; + return $message; + } + + /** + * Creates a human readable description of the + * difference between an integer and another variable. + * @param integer $first First number. + * @param mixed $second Number to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeIntegerDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + return "because [" . $this->describeValue($first) . + "] differs from [" . + $this->describeValue($second) . "] by " . + abs($first - $second); + } + + /** + * Creates a human readable description of the + * difference between two floating point numbers. + * @param float $first First float. + * @param mixed $second Float to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeFloatDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + return "because [" . $this->describeValue($first) . + "] differs from [" . + $this->describeValue($second) . "] by " . + abs($first - $second); + } + + /** + * Creates a human readable description of the + * difference between two arrays. + * @param array $first First array. + * @param mixed $second Array to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeArrayDifference($first, $second, $identical) { + if (! is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + if (! $this->isMatchingKeys($first, $second, $identical)) { + return "as key list [" . + implode(", ", array_keys($first)) . "] does not match key list [" . + implode(", ", array_keys($second)) . "]"; + } + foreach (array_keys($first) as $key) { + if ($identical && ($first[$key] === $second[$key])) { + continue; + } + if (! $identical && ($first[$key] == $second[$key])) { + continue; + } + return "with member [$key] " . $this->describeDifference( + $first[$key], + $second[$key], + $identical); + } + return ""; + } + + /** + * Compares two arrays to see if their key lists match. + * For an identical match, the ordering and types of the keys + * is significant. + * @param array $first First array. + * @param array $second Array to compare with. + * @param boolean $identical If true then type anomolies count. + * @return boolean True if matching. + * @access private + */ + protected function isMatchingKeys($first, $second, $identical) { + $first_keys = array_keys($first); + $second_keys = array_keys($second); + if ($identical) { + return ($first_keys === $second_keys); + } + sort($first_keys); + sort($second_keys); + return ($first_keys == $second_keys); + } + + /** + * Creates a human readable description of the + * difference between a resource and another variable. + * @param resource $first First resource. + * @param mixed $second Resource to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeResourceDifference($first, $second, $identical) { + return $this->describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between two objects. + * @param object $first First object. + * @param mixed $second Object to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeObjectDifference($first, $second, $identical) { + if (! is_object($second)) { + return $this->describeGenericDifference($first, $second); + } + return $this->describeArrayDifference( + get_object_vars($first), + get_object_vars($second), + $identical); + } + + /** + * Find the first character position that differs + * in two strings by binary chop. + * @param string $first First string. + * @param string $second String to compare with. + * @return integer Position of first differing + * character. + * @access private + */ + protected function stringDiffersAt($first, $second) { + if (! $first || ! $second) { + return 0; + } + if (strlen($first) < strlen($second)) { + list($first, $second) = array($second, $first); + } + $position = 0; + $step = strlen($first); + while ($step > 1) { + $step = (integer)(($step + 1) / 2); + if (strncmp($first, $second, $position + $step) == 0) { + $position += $step; + } + } + return $position; + } + + /** + * Sends a formatted dump of a variable to a string. + * @param mixed $variable Variable to display. + * @return string Output from print_r(). + * @access public + */ + function dump($variable) { + ob_start(); + print_r($variable); + $formatted = ob_get_contents(); + ob_end_clean(); + return $formatted; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/eclipse.php b/lib/Swift/test-suite/lib/simpletest/eclipse.php new file mode 100644 index 0000000..bd0349a --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/eclipse.php @@ -0,0 +1,307 @@ +listener = &$listener; + $this->SimpleScorer(); + $this->case = ""; + $this->group = ""; + $this->method = ""; + $this->cc = $cc; + $this->error = false; + $this->fail = false; + } + + /** + * Means to display human readable object comparisons. + * @return SimpleDumper Visual comparer. + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Localhost connection from Eclipse. + * @param integer $port Port to connect to Eclipse. + * @param string $host Normally localhost. + * @return SimpleSocket Connection to Eclipse. + */ + function &createListener($port, $host="127.0.0.1"){ + $tmplistener = &new SimpleSocket($host, $port, 5); + return $tmplistener; + } + + /** + * Wraps the test in an output buffer. + * @param SimpleInvoker $invoker Current test runner. + * @return EclipseInvoker Decorator with output buffering. + * @access public + */ + function &createInvoker(&$invoker){ + $eclinvoker = &new EclipseInvoker($invoker, $this->listener); + return $eclinvoker; + } + + /** + * C style escaping. + * @param string $raw String with backslashes, quotes and whitespace. + * @return string Replaced with C backslashed tokens. + */ + function escapeVal($raw){ + $needle = array("\\","\"","/","\b","\f","\n","\r","\t"); + $replace = array('\\\\','\"','\/','\b','\f','\n','\r','\t'); + return str_replace($needle, $replace, $raw); + } + + /** + * Stash the first passing item. Clicking the test + * item goes to first pass. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintPass($message){ + if (! $this->pass){ + $this->message = $this->escapeVal($message); + } + $this->pass = true; + } + + /** + * Stash the first failing item. Clicking the test + * item goes to first fail. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintFail($message){ + //only get the first failure or error + if (! $this->fail && ! $this->error){ + $this->fail = true; + $this->message = $this->escapeVal($message); + $this->listener->write('{status:"fail",message:"'.$this->message.'",group:"'.$this->group.'",case:"'.$this->case.'",method:"'.$this->method.'"}'); + } + } + + /** + * Stash the first error. Clicking the test + * item goes to first error. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintError($message){ + if (! $this->fail && ! $this->error){ + $this->error = true; + $this->message = $this->escapeVal($message); + $this->listener->write('{status:"error",message:"'.$this->message.'",group:"'.$this->group.'",case:"'.$this->case.'",method:"'.$this->method.'"}'); + } + } + + + /** + * Stash the first exception. Clicking the test + * item goes to first message. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintException($exception){ + if (! $this->fail && ! $this->error){ + $this->error = true; + $message = 'Unexpected exception of type[' . get_class($exception) . + '] with message [' . $exception->getMessage() . '] in [' . + $exception->getFile() .' line '. $exception->getLine() . ']'; + $this->message = $this->escapeVal($message); + $this->listener->write( + '{status:"error",message:"' . $this->message . '",group:"' . + $this->group . '",case:"' . $this->case . '",method:"' . $this->method + . '"}'); + } + } + + + /** + * We don't display any special header. + * @param string $test_name First test top level + * to start. + * @access public + */ + function paintHeader($test_name) { + } + + /** + * We don't display any special footer. + * @param string $test_name The top level test. + * @access public + */ + function paintFooter($test_name) { + } + + /** + * Paints nothing at the start of a test method, but stash + * the method name for later. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($method) { + $this->pass = false; + $this->fail = false; + $this->error = false; + $this->method = $this->escapeVal($method); + } + + /** + * Only send one message if the test passes, after that + * suppress the message. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintMethodEnd($method){ + if ($this->fail || $this->error || ! $this->pass){ + } else { + $this->listener->write( + '{status:"pass",message:"' . $this->message . '",group:"' . + $this->group . '",case:"' . $this->case . '",method:"' . + $this->method . '"}'); + } + } + + /** + * Stashes the test case name for the later failure message. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($case){ + $this->case = $this->escapeVal($case); + } + + /** + * Drops the name. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($case){ + $this->case = ""; + } + + /** + * Stashes the name of the test suite. Starts test coverage + * if enabled. + * @param string $group Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($group, $size){ + $this->group = $this->escapeVal($group); + if ($this->cc){ + if (extension_loaded('xdebug')){ + xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); + } + } + } + + /** + * Paints coverage report if enabled. + * @param string $group Name of test or other label. + * @access public + */ + function paintGroupEnd($group){ + $this->group = ""; + $cc = ""; + if ($this->cc){ + if (extension_loaded('xdebug')){ + $arrfiles = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + $thisdir = dirname(__FILE__); + $thisdirlen = strlen($thisdir); + foreach ($arrfiles as $index=>$file){ + if (substr($index, 0, $thisdirlen)===$thisdir){ + continue; + } + $lcnt = 0; + $ccnt = 0; + foreach ($file as $line){ + if ($line == -2){ + continue; + } + $lcnt++; + if ($line==1){ + $ccnt++; + } + } + if ($lcnt > 0){ + $cc .= round(($ccnt/$lcnt) * 100, 2) . '%'; + }else{ + $cc .= "0.00%"; + } + $cc.= "\t". $index . "\n"; + } + } + } + $this->listener->write('{status:"coverage",message:"' . + EclipseReporter::escapeVal($cc) . '"}'); + } +} + +/** + * Invoker decorator for Eclipse. Captures output until + * the end of the test. + * @package SimpleTest + * @subpackage Eclipse + */ +class EclipseInvoker extends SimpleInvokerDecorator{ + function __construct(&$invoker, &$listener) { + $this->listener = &$listener; + $this->SimpleInvokerDecorator($invoker); + } + + /** + * Starts output buffering. + * @param string $method Test method to call. + * @access public + */ + function before($method){ + ob_start(); + $this->invoker->before($method); + } + + /** + * Stops output buffering and send the captured output + * to the listener. + * @param string $method Test method to call. + * @access public + */ + function after($method) { + $this->invoker->after($method); + $output = ob_get_contents(); + ob_end_clean(); + if ($output !== ""){ + $result = $this->listener->write('{status:"info",message:"' . + EclipseReporter::escapeVal($output) . '"}'); + } + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/encoding.php b/lib/Swift/test-suite/lib/simpletest/encoding.php new file mode 100644 index 0000000..e44964d --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/encoding.php @@ -0,0 +1,552 @@ +key = $key; + $this->value = $value; + } + + /** + * The pair as a single string. + * @return string Encoded pair. + * @access public + */ + function asRequest() { + return urlencode($this->key) . '=' . urlencode($this->value); + } + + /** + * The MIME part as a string. + * @return string MIME part encoding. + * @access public + */ + function asMime() { + $part = 'Content-Disposition: form-data; '; + $part .= "name=\"" . $this->key . "\"\r\n"; + $part .= "\r\n" . $this->value; + return $part; + } + + /** + * Is this the value we are looking for? + * @param string $key Identifier. + * @return boolean True if matched. + * @access public + */ + function isKey($key) { + return $key == $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Identifier. + * @access public + */ + function getKey() { + return $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Content. + * @access public + */ + function getValue() { + return $this->value; + } +} + +/** + * Single post parameter. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAttachment { + private $key; + private $content; + private $filename; + + /** + * Stashes the data for rendering later. + * @param string $key Key to add value to. + * @param string $content Raw data. + * @param hash $filename Original filename. + */ + function __construct($key, $content, $filename) { + $this->key = $key; + $this->content = $content; + $this->filename = $filename; + } + + /** + * The pair as a single string. + * @return string Encoded pair. + * @access public + */ + function asRequest() { + return ''; + } + + /** + * The MIME part as a string. + * @return string MIME part encoding. + * @access public + */ + function asMime() { + $part = 'Content-Disposition: form-data; '; + $part .= 'name="' . $this->key . '"; '; + $part .= 'filename="' . $this->filename . '"'; + $part .= "\r\nContent-Type: " . $this->deduceMimeType(); + $part .= "\r\n\r\n" . $this->content; + return $part; + } + + /** + * Attempts to figure out the MIME type from the + * file extension and the content. + * @return string MIME type. + * @access private + */ + protected function deduceMimeType() { + if ($this->isOnlyAscii($this->content)) { + return 'text/plain'; + } + return 'application/octet-stream'; + } + + /** + * Tests each character is in the range 0-127. + * @param string $ascii String to test. + * @access private + */ + protected function isOnlyAscii($ascii) { + for ($i = 0, $length = strlen($ascii); $i < $length; $i++) { + if (ord($ascii[$i]) > 127) { + return false; + } + } + return true; + } + + /** + * Is this the value we are looking for? + * @param string $key Identifier. + * @return boolean True if matched. + * @access public + */ + function isKey($key) { + return $key == $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Identifier. + * @access public + */ + function getKey() { + return $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Content. + * @access public + */ + function getValue() { + return $this->filename; + } +} + +/** + * Bundle of GET/POST parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleEncoding { + private $request; + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false) { + if (! $query) { + $query = array(); + } + $this->clear(); + $this->merge($query); + } + + /** + * Empties the request of parameters. + * @access public + */ + function clear() { + $this->request = array(); + } + + /** + * Adds a parameter to the query. + * @param string $key Key to add value to. + * @param string/array $value New data. + * @access public + */ + function add($key, $value) { + if ($value === false) { + return; + } + if (is_array($value)) { + foreach ($value as $item) { + $this->addPair($key, $item); + } + } else { + $this->addPair($key, $value); + } + } + + /** + * Adds a new value into the request. + * @param string $key Key to add value to. + * @param string/array $value New data. + * @access private + */ + protected function addPair($key, $value) { + $this->request[] = new SimpleEncodedPair($key, $value); + } + + /** + * Adds a MIME part to the query. Does nothing for a + * form encoded packet. + * @param string $key Key to add value to. + * @param string $content Raw data. + * @param hash $filename Original filename. + * @access public + */ + function attach($key, $content, $filename) { + $this->request[] = new SimpleAttachment($key, $content, $filename); + } + + /** + * Adds a set of parameters to this query. + * @param array/SimpleQueryString $query Multiple values are + * as lists on a single key. + * @access public + */ + function merge($query) { + if (is_object($query)) { + $this->request = array_merge($this->request, $query->getAll()); + } elseif (is_array($query)) { + foreach ($query as $key => $value) { + $this->add($key, $value); + } + } + } + + /** + * Accessor for single value. + * @return string/array False if missing, string + * if present and array if + * multiple entries. + * @access public + */ + function getValue($key) { + $values = array(); + foreach ($this->request as $pair) { + if ($pair->isKey($key)) { + $values[] = $pair->getValue(); + } + } + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Accessor for listing of pairs. + * @return array All pair objects. + * @access public + */ + function getAll() { + return $this->request; + } + + /** + * Renders the query string as a URL encoded + * request part. + * @return string Part of URL. + * @access protected + */ + protected function encode() { + $statements = array(); + foreach ($this->request as $pair) { + if ($statement = $pair->asRequest()) { + $statements[] = $statement; + } + } + return implode('&', $statements); + } +} + +/** + * Bundle of GET parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleGetEncoding extends SimpleEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false) { + parent::__construct($query); + } + + /** + * HTTP request method. + * @return string Always GET. + * @access public + */ + function getMethod() { + return 'GET'; + } + + /** + * Writes no extra headers. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + } + + /** + * No data is sent to the socket as the data is encoded into + * the URL. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + } + + /** + * Renders the query string as a URL encoded + * request part for attaching to a URL. + * @return string Part of URL. + * @access public + */ + function asUrlRequest() { + return $this->encode(); + } +} + +/** + * Bundle of URL parameters for a HEAD request. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHeadEncoding extends SimpleGetEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function SimpleHeadEncoding($query = false) { + $this->SimpleGetEncoding($query); + } + + /** + * HTTP request method. + * @return string Always HEAD. + * @access public + */ + function getMethod() { + return 'HEAD'; + } +} + +/** + * Bundle of POST parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePostEncoding extends SimpleEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false) { + if (is_array($query) and $this->hasMoreThanOneLevel($query)) { + $query = $this->rewriteArrayWithMultipleLevels($query); + } + parent::__construct($query); + } + + function hasMoreThanOneLevel($query) { + foreach ($query as $key => $value) { + if (is_array($value)) { + return true; + } + } + return false; + } + + function rewriteArrayWithMultipleLevels($query) { + $query_ = array(); + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $sub_key => $sub_value) { + $query_[$key."[".$sub_key."]"] = $sub_value; + } + } else { + $query_[$key] = $value; + } + } + if ($this->hasMoreThanOneLevel($query_)) { + $query_ = $this->rewriteArrayWithMultipleLevels($query_); + } + + return $query_; + } + + + /** + * HTTP request method. + * @return string Always POST. + * @access public + */ + function getMethod() { + return 'POST'; + } + + /** + * Dispatches the form headers down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + $socket->write("Content-Length: " . (integer)strlen($this->encode()) . "\r\n"); + $socket->write("Content-Type: application/x-www-form-urlencoded\r\n"); + } + + /** + * Dispatches the form data down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + $socket->write($this->encode()); + } + + /** + * Renders the query string as a URL encoded + * request part for attaching to a URL. + * @return string Part of URL. + * @access public + */ + function asUrlRequest() { + return ''; + } +} + +/** + * Bundle of POST parameters in the multipart + * format. Can include file uploads. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleMultipartEncoding extends SimplePostEncoding { + private $boundary; + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false, $boundary = false) { + parent::__construct($query); + $this->boundary = ($boundary === false ? uniqid('st') : $boundary); + } + + /** + * Dispatches the form headers down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + $socket->write("Content-Length: " . (integer)strlen($this->encode()) . "\r\n"); + $socket->write("Content-Type: multipart/form-data, boundary=" . $this->boundary . "\r\n"); + } + + /** + * Dispatches the form data down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + $socket->write($this->encode()); + } + + /** + * Renders the query string as a URL encoded + * request part. + * @return string Part of URL. + * @access public + */ + function encode() { + $stream = ''; + foreach ($this->getAll() as $pair) { + $stream .= "--" . $this->boundary . "\r\n"; + $stream .= $pair->asMime() . "\r\n"; + } + $stream .= "--" . $this->boundary . "--\r\n"; + return $stream; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/errors.php b/lib/Swift/test-suite/lib/simpletest/errors.php new file mode 100644 index 0000000..52385ee --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/errors.php @@ -0,0 +1,257 @@ +createErrorQueue(); + set_error_handler('SimpleTestErrorHandler'); + parent::invoke($method); + restore_error_handler(); + $queue->tally(); + } + + /** + * Wires up the error queue for a single test. + * @return SimpleErrorQueue Queue connected to the test. + * @access private + */ + protected function createErrorQueue() { + $context = SimpleTest::getContext(); + $test = $this->getTestCase(); + $queue = $context->get('SimpleErrorQueue'); + $queue->setTestCase($test); + return $queue; + } +} + +/** + * Error queue used to record trapped + * errors. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleErrorQueue { + private $queue; + private $expectation_queue; + private $test; + private $using_expect_style = false; + + /** + * Starts with an empty queue. + */ + function __construct() { + $this->clear(); + } + + /** + * Discards the contents of the error queue. + * @access public + */ + function clear() { + $this->queue = array(); + $this->expectation_queue = array(); + } + + /** + * Sets the currently running test case. + * @param SimpleTestCase $test Test case to send messages to. + * @access public + */ + function setTestCase($test) { + $this->test = $test; + } + + /** + * Sets up an expectation of an error. If this is + * not fulfilled at the end of the test, a failure + * will occour. If the error does happen, then this + * will cancel it out and send a pass message. + * @param SimpleExpectation $expected Expected error match. + * @param string $message Message to display. + * @access public + */ + function expectError($expected, $message) { + array_push($this->expectation_queue, array($expected, $message)); + } + + /** + * Adds an error to the front of the queue. + * @param integer $severity PHP error code. + * @param string $content Text of error. + * @param string $filename File error occoured in. + * @param integer $line Line number of error. + * @access public + */ + function add($severity, $content, $filename, $line) { + $content = str_replace('%', '%%', $content); + $this->testLatestError($severity, $content, $filename, $line); + } + + /** + * Any errors still in the queue are sent to the test + * case. Any unfulfilled expectations trigger failures. + * @access public + */ + function tally() { + while (list($severity, $message, $file, $line) = $this->extract()) { + $severity = $this->getSeverityAsString($severity); + $this->test->error($severity, $message, $file, $line); + } + while (list($expected, $message) = $this->extractExpectation()) { + $this->test->assert($expected, false, "%s -> Expected error not caught"); + } + } + + /** + * Tests the error against the most recent expected + * error. + * @param integer $severity PHP error code. + * @param string $content Text of error. + * @param string $filename File error occoured in. + * @param integer $line Line number of error. + * @access private + */ + protected function testLatestError($severity, $content, $filename, $line) { + if ($expectation = $this->extractExpectation()) { + list($expected, $message) = $expectation; + $this->test->assert($expected, $content, sprintf( + $message, + "%s -> PHP error [$content] severity [" . + $this->getSeverityAsString($severity) . + "] in [$filename] line [$line]")); + } else { + $this->test->error($severity, $content, $filename, $line); + } + } + + /** + * Pulls the earliest error from the queue. + * @return mixed False if none, or a list of error + * information. Elements are: severity + * as the PHP error code, the error message, + * the file with the error, the line number + * and a list of PHP super global arrays. + * @access public + */ + function extract() { + if (count($this->queue)) { + return array_shift($this->queue); + } + return false; + } + + /** + * Pulls the earliest expectation from the queue. + * @return SimpleExpectation False if none. + * @access private + */ + protected function extractExpectation() { + if (count($this->expectation_queue)) { + return array_shift($this->expectation_queue); + } + return false; + } + + /** + * Converts an error code into it's string + * representation. + * @param $severity PHP integer error code. + * @return String version of error code. + * @access public + */ + static function getSeverityAsString($severity) { + static $map = array( + E_STRICT => 'E_STRICT', + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE'); + if (defined('E_RECOVERABLE_ERROR')) { + $map[E_RECOVERABLE_ERROR] = 'E_RECOVERABLE_ERROR'; + } + if (defined('E_DEPRECATED')) { + $map[E_DEPRECATED] = 'E_DEPRECATED'; + } + return $map[$severity]; + } +} + +/** + * Error handler that simply stashes any errors into the global + * error queue. Simulates the existing behaviour with respect to + * logging errors, but this feature may be removed in future. + * @param $severity PHP error code. + * @param $message Text of error. + * @param $filename File error occoured in. + * @param $line Line number of error. + * @param $super_globals Hash of PHP super global arrays. + * @access public + */ +function SimpleTestErrorHandler($severity, $message, $filename = null, $line = null, $super_globals = null, $mask = null) { + $severity = $severity & error_reporting(); + if ($severity) { + restore_error_handler(); + if (IsNotCausedBySimpleTest($message)) { + if (ini_get('log_errors')) { + $label = SimpleErrorQueue::getSeverityAsString($severity); + error_log("$label: $message in $filename on line $line"); + } + $queue = SimpleTest::getContext()->get('SimpleErrorQueue'); + $queue->add($severity, $message, $filename, $line); + } + set_error_handler('SimpleTestErrorHandler'); + } + return true; +} + +/** + * Certain messages can be caused by the unit tester itself. + * These have to be filtered. + * @param string $message Message to filter. + * @return boolean True if genuine failure. + */ +function IsNotCausedBySimpleTest($message) { + return ! preg_match('/returned by reference/', $message); +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/exceptions.php b/lib/Swift/test-suite/lib/simpletest/exceptions.php new file mode 100644 index 0000000..984015d --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/exceptions.php @@ -0,0 +1,198 @@ +get('SimpleExceptionTrap'); + $trap->clear(); + try { + $has_thrown = false; + parent::invoke($method); + } catch (Exception $exception) { + $has_thrown = true; + if (! $trap->isExpected($this->getTestCase(), $exception)) { + $this->getTestCase()->exception($exception); + } + $trap->clear(); + } + if ($message = $trap->getOutstanding()) { + $this->getTestCase()->fail($message); + } + if ($has_thrown) { + try { + parent::getTestCase()->tearDown(); + } catch (Exception $e) { } + } + } +} + +/** + * Tests exceptions either by type or the exact + * exception. This could be improved to accept + * a pattern expectation to test the error + * message, but that will have to come later. + * @package SimpleTest + * @subpackage UnitTester + */ +class ExceptionExpectation extends SimpleExpectation { + private $expected; + + /** + * Sets up the conditions to test against. + * If the expected value is a string, then + * it will act as a test of the class name. + * An exception as the comparison will + * trigger an identical match. Writing this + * down now makes it look doubly dumb. I hope + * come up with a better scheme later. + * @param mixed $expected A class name or an actual + * exception to compare with. + * @param string $message Message to display. + */ + function __construct($expected, $message = '%s') { + $this->expected = $expected; + parent::__construct($message); + } + + /** + * Carry out the test. + * @param Exception $compare Value to check. + * @return boolean True if matched. + */ + function test($compare) { + if (is_string($this->expected)) { + return ($compare instanceof $this->expected); + } + if (get_class($compare) != get_class($this->expected)) { + return false; + } + return $compare->getMessage() == $this->expected->getMessage(); + } + + /** + * Create the message to display describing the test. + * @param Exception $compare Exception to match. + * @return string Final message. + */ + function testMessage($compare) { + if (is_string($this->expected)) { + return "Exception [" . $this->describeException($compare) . + "] should be type [" . $this->expected . "]"; + } + return "Exception [" . $this->describeException($compare) . + "] should match [" . + $this->describeException($this->expected) . "]"; + } + + /** + * Summary of an Exception object. + * @param Exception $compare Exception to describe. + * @return string Text description. + */ + protected function describeException($exception) { + return get_class($exception) . ": " . $exception->getMessage(); + } +} + +/** + * Stores expected exceptions for when they + * get thrown. Saves the irritating try...catch + * block. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleExceptionTrap { + private $expected; + private $message; + + /** + * Clears down the queue ready for action. + */ + function __construct() { + $this->clear(); + } + + /** + * Sets up an expectation of an exception. + * This has the effect of intercepting an + * exception that matches. + * @param SimpleExpectation $expected Expected exception to match. + * @param string $message Message to display. + * @access public + */ + function expectException($expected = false, $message = '%s') { + if ($expected === false) { + $expected = new AnythingExpectation(); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new ExceptionExpectation($expected); + } + $this->expected = $expected; + $this->message = $message; + } + + /** + * Compares the expected exception with any + * in the queue. Issues a pass or fail and + * returns the state of the test. + * @param SimpleTestCase $test Test case to send messages to. + * @param Exception $exception Exception to compare. + * @return boolean False on no match. + */ + function isExpected($test, $exception) { + if ($this->expected) { + return $test->assert($this->expected, $exception, $this->message); + } + return false; + } + + /** + * Tests for any left over exception. + * @return string/false The failure message or false if none. + */ + function getOutstanding() { + return sprintf($this->message, 'Failed to trap exception'); + } + + /** + * Discards the contents of the error queue. + */ + function clear() { + $this->expected = false; + $this->message = false; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/expectation.php b/lib/Swift/test-suite/lib/simpletest/expectation.php new file mode 100644 index 0000000..97daa97 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/expectation.php @@ -0,0 +1,901 @@ +message = $message; + } + + /** + * Tests the expectation. True if correct. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + * @abstract + */ + function test($compare) { + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + * @abstract + */ + function testMessage($compare) { + } + + /** + * Overlays the generated message onto the stored user + * message. An additional message can be interjected. + * @param mixed $compare Comparison value. + * @param SimpleDumper $dumper For formatting the results. + * @return string Description of success + * or failure. + * @access public + */ + function overlayMessage($compare, $dumper) { + $this->dumper = $dumper; + return sprintf($this->message, $this->testMessage($compare)); + } + + /** + * Accessor for the dumper. + * @return SimpleDumper Current value dumper. + * @access protected + */ + protected function getDumper() { + if (! $this->dumper) { + $dumper = new SimpleDumper(); + return $dumper; + } + return $this->dumper; + } + + /** + * Test to see if a value is an expectation object. + * A useful utility method. + * @param mixed $expectation Hopefully an Expectation + * class. + * @return boolean True if descended from + * this class. + * @access public + */ + static function isExpectation($expectation) { + return is_object($expectation) && + SimpleTestCompatibility::isA($expectation, 'SimpleExpectation'); + } +} + +/** + * A wildcard expectation always matches. + * @package SimpleTest + * @subpackage MockObjects + */ +class AnythingExpectation extends SimpleExpectation { + + /** + * Tests the expectation. Always true. + * @param mixed $compare Ignored. + * @return boolean True. + * @access public + */ + function test($compare) { + return true; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Anything always matches [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * An expectation that never matches. + * @package SimpleTest + * @subpackage MockObjects + */ +class FailedExpectation extends SimpleExpectation { + + /** + * Tests the expectation. Always false. + * @param mixed $compare Ignored. + * @return boolean True. + * @access public + */ + function test($compare) { + return false; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Failed expectation never matches [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * An expectation that passes on boolean true. + * @package SimpleTest + * @subpackage MockObjects + */ +class TrueExpectation extends SimpleExpectation { + + /** + * Tests the expectation. + * @param mixed $compare Should be true. + * @return boolean True on match. + * @access public + */ + function test($compare) { + return (boolean)$compare; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Expected true, got [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * An expectation that passes on boolean false. + * @package SimpleTest + * @subpackage MockObjects + */ +class FalseExpectation extends SimpleExpectation { + + /** + * Tests the expectation. + * @param mixed $compare Should be false. + * @return boolean True on match. + * @access public + */ + function test($compare) { + return ! (boolean)$compare; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Expected false, got [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * Test for equality. + * @package SimpleTest + * @subpackage UnitTester + */ +class EqualExpectation extends SimpleExpectation { + private $value; + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($message); + $this->value = $value; + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (($this->value == $compare) && ($compare == $this->value)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return "Equal expectation [" . $this->dumper->describeValue($this->value) . "]"; + } else { + return "Equal expectation fails " . + $this->dumper->describeDifference($this->value, $compare); + } + } + + /** + * Accessor for comparison value. + * @return mixed Held value to compare with. + * @access protected + */ + protected function getValue() { + return $this->value; + } +} + +/** + * Test for inequality. + * @package SimpleTest + * @subpackage UnitTester + */ +class NotEqualExpectation extends EqualExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($value, $message); + } + + /** + * Tests the expectation. True if it differs from the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if ($this->test($compare)) { + return "Not equal expectation passes " . + $dumper->describeDifference($this->getValue(), $compare); + } else { + return "Not equal expectation fails [" . + $dumper->describeValue($this->getValue()) . + "] matches"; + } + } +} + +/** + * Test for being within a range. + * @package SimpleTest + * @subpackage UnitTester + */ +class WithinMarginExpectation extends SimpleExpectation { + private $upper; + private $lower; + + /** + * Sets the value to compare against and the fuzziness of + * the match. Used for comparing floating point values. + * @param mixed $value Test value to match. + * @param mixed $margin Fuzziness of match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $margin, $message = '%s') { + parent::__construct($message); + $this->upper = $value + $margin; + $this->lower = $value - $margin; + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (($compare <= $this->upper) && ($compare >= $this->lower)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return $this->withinMessage($compare); + } else { + return $this->outsideMessage($compare); + } + } + + /** + * Creates a the message for being within the range. + * @param mixed $compare Value being tested. + * @access private + */ + protected function withinMessage($compare) { + return "Within expectation [" . $this->dumper->describeValue($this->lower) . "] and [" . + $this->dumper->describeValue($this->upper) . "]"; + } + + /** + * Creates a the message for being within the range. + * @param mixed $compare Value being tested. + * @access private + */ + protected function outsideMessage($compare) { + if ($compare > $this->upper) { + return "Outside expectation " . + $this->dumper->describeDifference($compare, $this->upper); + } else { + return "Outside expectation " . + $this->dumper->describeDifference($compare, $this->lower); + } + } +} + +/** + * Test for being outside of a range. + * @package SimpleTest + * @subpackage UnitTester + */ +class OutsideMarginExpectation extends WithinMarginExpectation { + + /** + * Sets the value to compare against and the fuzziness of + * the match. Used for comparing floating point values. + * @param mixed $value Test value to not match. + * @param mixed $margin Fuzziness of match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $margin, $message = '%s') { + parent::__construct($value, $margin, $message); + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if (! $this->test($compare)) { + return $this->withinMessage($compare); + } else { + return $this->outsideMessage($compare); + } + } +} + +/** + * Test for reference. + * @package SimpleTest + * @subpackage UnitTester + */ +class ReferenceExpectation { + private $value; + + /** + * Sets the reference value to compare against. + * @param mixed $value Test reference to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct(&$value, $message = '%s') { + $this->message = $message; + $this->value = &$value; + } + + /** + * Tests the expectation. True if it exactly + * references the held value. + * @param mixed $compare Comparison reference. + * @return boolean True if correct. + * @access public + */ + function test(&$compare) { + return SimpleTestCompatibility::isReference($this->value, $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return "Reference expectation [" . $this->dumper->describeValue($this->value) . "]"; + } else { + return "Reference expectation fails " . + $this->dumper->describeDifference($this->value, $compare); + } + } + + /** + * Overlays the generated message onto the stored user + * message. An additional message can be interjected. + * @param mixed $compare Comparison value. + * @param SimpleDumper $dumper For formatting the results. + * @return string Description of success + * or failure. + * @access public + */ + function overlayMessage($compare, $dumper) { + $this->dumper = $dumper; + return sprintf($this->message, $this->testMessage($compare)); + } + + /** + * Accessor for the dumper. + * @return SimpleDumper Current value dumper. + * @access protected + */ + protected function getDumper() { + if (! $this->dumper) { + $dumper = new SimpleDumper(); + return $dumper; + } + return $this->dumper; + } +} + +/** + * Test for identity. + * @package SimpleTest + * @subpackage UnitTester + */ +class IdenticalExpectation extends EqualExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($value, $message); + } + + /** + * Tests the expectation. True if it exactly + * matches the held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return SimpleTestCompatibility::isIdentical($this->getValue(), $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if ($this->test($compare)) { + return "Identical expectation [" . $dumper->describeValue($this->getValue()) . "]"; + } else { + return "Identical expectation [" . $dumper->describeValue($this->getValue()) . + "] fails with [" . + $dumper->describeValue($compare) . "] " . + $dumper->describeDifference($this->getValue(), $compare, TYPE_MATTERS); + } + } +} + +/** + * Test for non-identity. + * @package SimpleTest + * @subpackage UnitTester + */ +class NotIdenticalExpectation extends IdenticalExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($value, $message); + } + + /** + * Tests the expectation. True if it differs from the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if ($this->test($compare)) { + return "Not identical expectation passes " . + $dumper->describeDifference($this->getValue(), $compare, TYPE_MATTERS); + } else { + return "Not identical expectation [" . $dumper->describeValue($this->getValue()) . "] matches"; + } + } +} + +/** + * Test for a pattern using Perl regex rules. + * @package SimpleTest + * @subpackage UnitTester + */ +class PatternExpectation extends SimpleExpectation { + private $pattern; + + /** + * Sets the value to compare against. + * @param string $pattern Pattern to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($pattern, $message = '%s') { + parent::__construct($message); + $this->pattern = $pattern; + } + + /** + * Accessor for the pattern. + * @return string Perl regex as string. + * @access protected + */ + protected function getPattern() { + return $this->pattern; + } + + /** + * Tests the expectation. True if the Perl regex + * matches the comparison value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (boolean)preg_match($this->getPattern(), $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return $this->describePatternMatch($this->getPattern(), $compare); + } else { + $dumper = $this->getDumper(); + return "Pattern [" . $this->getPattern() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } + } + + /** + * Describes a pattern match including the string + * found and it's position. + * @param string $pattern Regex to match against. + * @param string $subject Subject to search. + * @access protected + */ + protected function describePatternMatch($pattern, $subject) { + preg_match($pattern, $subject, $matches); + $position = strpos($subject, $matches[0]); + $dumper = $this->getDumper(); + return "Pattern [$pattern] detected at character [$position] in [" . + $dumper->describeValue($subject) . "] as [" . + $matches[0] . "] in region [" . + $dumper->clipString($subject, 100, $position) . "]"; + } +} + +/** + * Fail if a pattern is detected within the + * comparison. + * @package SimpleTest + * @subpackage UnitTester + */ +class NoPatternExpectation extends PatternExpectation { + + /** + * Sets the reject pattern + * @param string $pattern Pattern to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($pattern, $message = '%s') { + parent::__construct($pattern, $message); + } + + /** + * Tests the expectation. False if the Perl regex + * matches the comparison value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param string $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + $dumper = $this->getDumper(); + return "Pattern [" . $this->getPattern() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } else { + return $this->describePatternMatch($this->getPattern(), $compare); + } + } +} + +/** + * Tests either type or class name if it's an object. + * @package SimpleTest + * @subpackage UnitTester + */ +class IsAExpectation extends SimpleExpectation { + private $type; + + /** + * Sets the type to compare with. + * @param string $type Type or class name. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($type, $message = '%s') { + parent::__construct($message); + $this->type = $type; + } + + /** + * Accessor for type to check against. + * @return string Type or class name. + * @access protected + */ + protected function getType() { + return $this->type; + } + + /** + * Tests the expectation. True if the type or + * class matches the string value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + if (is_object($compare)) { + return SimpleTestCompatibility::isA($compare, $this->type); + } else { + return (strtolower(gettype($compare)) == $this->canonicalType($this->type)); + } + } + + /** + * Coerces type name into a gettype() match. + * @param string $type User type. + * @return string Simpler type. + * @access private + */ + protected function canonicalType($type) { + $type = strtolower($type); + $map = array( + 'bool' => 'boolean', + 'float' => 'double', + 'real' => 'double', + 'int' => 'integer'); + if (isset($map[$type])) { + $type = $map[$type]; + } + return $type; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return "Value [" . $dumper->describeValue($compare) . + "] should be type [" . $this->type . "]"; + } +} + +/** + * Tests either type or class name if it's an object. + * Will succeed if the type does not match. + * @package SimpleTest + * @subpackage UnitTester + */ +class NotAExpectation extends IsAExpectation { + private $type; + + /** + * Sets the type to compare with. + * @param string $type Type or class name. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($type, $message = '%s') { + parent::__construct($type, $message); + } + + /** + * Tests the expectation. False if the type or + * class matches the string value. + * @param string $compare Comparison value. + * @return boolean True if different. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return "Value [" . $dumper->describeValue($compare) . + "] should not be type [" . $this->getType() . "]"; + } +} + +/** + * Tests for existance of a method in an object + * @package SimpleTest + * @subpackage UnitTester + */ +class MethodExistsExpectation extends SimpleExpectation { + private $method; + + /** + * Sets the value to compare against. + * @param string $method Method to check. + * @param string $message Customised message on failure. + * @access public + * @return void + */ + function __construct($method, $message = '%s') { + parent::__construct($message); + $this->method = &$method; + } + + /** + * Tests the expectation. True if the method exists in the test object. + * @param string $compare Comparison method name. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (boolean)(is_object($compare) && method_exists($compare, $this->method)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if (! is_object($compare)) { + return 'No method on non-object [' . $dumper->describeValue($compare) . ']'; + } + $method = $this->method; + return "Object [" . $dumper->describeValue($compare) . + "] should contain method [$method]"; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/form.php b/lib/Swift/test-suite/lib/simpletest/form.php new file mode 100644 index 0000000..d14f0f7 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/form.php @@ -0,0 +1,355 @@ +method = $tag->getAttribute('method'); + $this->action = $this->createAction($tag->getAttribute('action'), $page); + $this->encoding = $this->setEncodingClass($tag); + $this->default_target = false; + $this->id = $tag->getAttribute('id'); + $this->buttons = array(); + $this->images = array(); + $this->widgets = array(); + $this->radios = array(); + $this->checkboxes = array(); + } + + /** + * Creates the request packet to be sent by the form. + * @param SimpleTag $tag Form tag to read. + * @return string Packet class. + * @access private + */ + protected function setEncodingClass($tag) { + if (strtolower($tag->getAttribute('method')) == 'post') { + if (strtolower($tag->getAttribute('enctype')) == 'multipart/form-data') { + return 'SimpleMultipartEncoding'; + } + return 'SimplePostEncoding'; + } + return 'SimpleGetEncoding'; + } + + /** + * Sets the frame target within a frameset. + * @param string $frame Name of frame. + * @access public + */ + function setDefaultTarget($frame) { + $this->default_target = $frame; + } + + /** + * Accessor for method of form submission. + * @return string Either get or post. + * @access public + */ + function getMethod() { + return ($this->method ? strtolower($this->method) : 'get'); + } + + /** + * Combined action attribute with current location + * to get an absolute form target. + * @param string $action Action attribute from form tag. + * @param SimpleUrl $base Page location. + * @return SimpleUrl Absolute form target. + */ + protected function createAction($action, $page) { + if (($action === '') || ($action === false)) { + return $page->expandUrl($page->getUrl()); + } + return $page->expandUrl(new SimpleUrl($action));; + } + + /** + * Absolute URL of the target. + * @return SimpleUrl URL target. + * @access public + */ + function getAction() { + $url = $this->action; + if ($this->default_target && ! $url->getTarget()) { + $url->setTarget($this->default_target); + } + return $url; + } + + /** + * Creates the encoding for the current values in the + * form. + * @return SimpleFormEncoding Request to submit. + * @access private + */ + protected function encode() { + $class = $this->encoding; + $encoding = new $class(); + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + $this->widgets[$i]->write($encoding); + } + return $encoding; + } + + /** + * ID field of form for unique identification. + * @return string Unique tag ID. + * @access public + */ + function getId() { + return $this->id; + } + + /** + * Adds a tag contents to the form. + * @param SimpleWidget $tag Input tag to add. + * @access public + */ + function addWidget($tag) { + if (strtolower($tag->getAttribute('type')) == 'submit') { + $this->buttons[] = $tag; + } elseif (strtolower($tag->getAttribute('type')) == 'image') { + $this->images[] = $tag; + } elseif ($tag->getName()) { + $this->setWidget($tag); + } + } + + /** + * Sets the widget into the form, grouping radio + * buttons if any. + * @param SimpleWidget $tag Incoming form control. + * @access private + */ + protected function setWidget($tag) { + if (strtolower($tag->getAttribute('type')) == 'radio') { + $this->addRadioButton($tag); + } elseif (strtolower($tag->getAttribute('type')) == 'checkbox') { + $this->addCheckbox($tag); + } else { + $this->widgets[] = &$tag; + } + } + + /** + * Adds a radio button, building a group if necessary. + * @param SimpleRadioButtonTag $tag Incoming form control. + * @access private + */ + protected function addRadioButton($tag) { + if (! isset($this->radios[$tag->getName()])) { + $this->widgets[] = new SimpleRadioGroup(); + $this->radios[$tag->getName()] = count($this->widgets) - 1; + } + $this->widgets[$this->radios[$tag->getName()]]->addWidget($tag); + } + + /** + * Adds a checkbox, making it a group on a repeated name. + * @param SimpleCheckboxTag $tag Incoming form control. + * @access private + */ + protected function addCheckbox($tag) { + if (! isset($this->checkboxes[$tag->getName()])) { + $this->widgets[] = $tag; + $this->checkboxes[$tag->getName()] = count($this->widgets) - 1; + } else { + $index = $this->checkboxes[$tag->getName()]; + if (! SimpleTestCompatibility::isA($this->widgets[$index], 'SimpleCheckboxGroup')) { + $previous = $this->widgets[$index]; + $this->widgets[$index] = new SimpleCheckboxGroup(); + $this->widgets[$index]->addWidget($previous); + } + $this->widgets[$index]->addWidget($tag); + } + } + + /** + * Extracts current value from form. + * @param SimpleSelector $selector Criteria to apply. + * @return string/array Value(s) as string or null + * if not set. + * @access public + */ + function getValue($selector) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($selector->isMatch($this->widgets[$i])) { + return $this->widgets[$i]->getValue(); + } + } + foreach ($this->buttons as $button) { + if ($selector->isMatch($button)) { + return $button->getValue(); + } + } + return null; + } + + /** + * Sets a widget value within the form. + * @param SimpleSelector $selector Criteria to apply. + * @param string $value Value to input into the widget. + * @return boolean True if value is legal, false + * otherwise. If the field is not + * present, nothing will be set. + * @access public + */ + function setField($selector, $value, $position=false) { + $success = false; + $_position = 0; + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($selector->isMatch($this->widgets[$i])) { + $_position++; + if ($position === false or $_position === (int)$position) { + if ($this->widgets[$i]->setValue($value)) { + $success = true; + } + } + } + } + return $success; + } + + /** + * Used by the page object to set widgets labels to + * external label tags. + * @param SimpleSelector $selector Criteria to apply. + * @access public + */ + function attachLabelBySelector($selector, $label) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($selector->isMatch($this->widgets[$i])) { + if (method_exists($this->widgets[$i], 'setLabel')) { + $this->widgets[$i]->setLabel($label); + return; + } + } + } + } + + /** + * Test to see if a form has a submit button. + * @param SimpleSelector $selector Criteria to apply. + * @return boolean True if present. + * @access public + */ + function hasSubmit($selector) { + foreach ($this->buttons as $button) { + if ($selector->isMatch($button)) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has an image control. + * @param SimpleSelector $selector Criteria to apply. + * @return boolean True if present. + * @access public + */ + function hasImage($selector) { + foreach ($this->images as $image) { + if ($selector->isMatch($image)) { + return true; + } + } + return false; + } + + /** + * Gets the submit values for a selected button. + * @param SimpleSelector $selector Criteria to apply. + * @param hash $additional Additional data for the form. + * @return SimpleEncoding Submitted values or false + * if there is no such button + * in the form. + * @access public + */ + function submitButton($selector, $additional = false) { + $additional = $additional ? $additional : array(); + foreach ($this->buttons as $button) { + if ($selector->isMatch($button)) { + $encoding = $this->encode(); + $button->write($encoding); + if ($additional) { + $encoding->merge($additional); + } + return $encoding; + } + } + return false; + } + + /** + * Gets the submit values for an image. + * @param SimpleSelector $selector Criteria to apply. + * @param integer $x X-coordinate of click. + * @param integer $y Y-coordinate of click. + * @param hash $additional Additional data for the form. + * @return SimpleEncoding Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitImage($selector, $x, $y, $additional = false) { + $additional = $additional ? $additional : array(); + foreach ($this->images as $image) { + if ($selector->isMatch($image)) { + $encoding = $this->encode(); + $image->write($encoding, $x, $y); + if ($additional) { + $encoding->merge($additional); + } + return $encoding; + } + } + return false; + } + + /** + * Simply submits the form without the submit button + * value. Used when there is only one button or it + * is unimportant. + * @return hash Submitted values. + * @access public + */ + function submit() { + return $this->encode(); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/frames.php b/lib/Swift/test-suite/lib/simpletest/frames.php new file mode 100644 index 0000000..d6d8e96 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/frames.php @@ -0,0 +1,592 @@ +frameset = $page; + $this->frames = array(); + $this->focus = false; + $this->names = array(); + } + + /** + * Adds a parsed page to the frameset. + * @param SimplePage $page Frame page. + * @param string $name Name of frame in frameset. + * @access public + */ + function addFrame($page, $name = false) { + $this->frames[] = $page; + if ($name) { + $this->names[$name] = count($this->frames) - 1; + } + } + + /** + * Replaces existing frame with another. If the + * frame is nested, then the call is passed down + * one level. + * @param array $path Path of frame in frameset. + * @param SimplePage $page Frame source. + * @access public + */ + function setFrame($path, $page) { + $name = array_shift($path); + if (isset($this->names[$name])) { + $index = $this->names[$name]; + } else { + $index = $name - 1; + } + if (count($path) == 0) { + $this->frames[$index] = &$page; + return; + } + $this->frames[$index]->setFrame($path, $page); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. Will have the nested + * frame focus if any. + * @return array Labels or indexes of nested frames. + * @access public + */ + function getFrameFocus() { + if ($this->focus === false) { + return array(); + } + return array_merge( + array($this->getPublicNameFromIndex($this->focus)), + $this->frames[$this->focus]->getFrameFocus()); + } + + /** + * Turns an internal array index into the frames list + * into a public name, or if none, then a one offset + * index. + * @param integer $subject Internal index. + * @return integer/string Public name. + * @access private + */ + protected function getPublicNameFromIndex($subject) { + foreach ($this->names as $name => $index) { + if ($subject == $index) { + return $name; + } + } + return $subject + 1; + } + + /** + * Sets the focus by index. The integer index starts from 1. + * If already focused and the target frame also has frames, + * then the nested frame will be focused. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + if (is_integer($this->focus)) { + if ($this->frames[$this->focus]->hasFrames()) { + return $this->frames[$this->focus]->setFrameFocusByIndex($choice); + } + } + if (($choice < 1) || ($choice > count($this->frames))) { + return false; + } + $this->focus = $choice - 1; + return true; + } + + /** + * Sets the focus by name. If already focused and the + * target frame also has frames, then the nested frame + * will be focused. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + if (is_integer($this->focus)) { + if ($this->frames[$this->focus]->hasFrames()) { + return $this->frames[$this->focus]->setFrameFocus($name); + } + } + if (in_array($name, array_keys($this->names))) { + $this->focus = $this->names[$name]; + return true; + } + return false; + } + + /** + * Clears the frame focus. + * @access public + */ + function clearFrameFocus() { + $this->focus = false; + $this->clearNestedFramesFocus(); + } + + /** + * Clears the frame focus for any nested frames. + * @access private + */ + protected function clearNestedFramesFocus() { + for ($i = 0; $i < count($this->frames); $i++) { + $this->frames[$i]->clearFrameFocus(); + } + } + + /** + * Test for the presence of a frameset. + * @return boolean Always true. + * @access public + */ + function hasFrames() { + return true; + } + + /** + * Accessor for frames information. + * @return array/string Recursive hash of frame URL strings. + * The key is either a numerical + * index or the name attribute. + * @access public + */ + function getFrames() { + $report = array(); + for ($i = 0; $i < count($this->frames); $i++) { + $report[$this->getPublicNameFromIndex($i)] = + $this->frames[$i]->getFrames(); + } + return $report; + } + + /** + * Accessor for raw text of either all the pages or + * the frame in focus. + * @return string Raw unparsed content. + * @access public + */ + function getRaw() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRaw(); + } + $raw = ''; + for ($i = 0; $i < count($this->frames); $i++) { + $raw .= $this->frames[$i]->getRaw(); + } + return $raw; + } + + /** + * Accessor for plain text of either all the pages or + * the frame in focus. + * @return string Plain text content. + * @access public + */ + function getText() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getText(); + } + $raw = ''; + for ($i = 0; $i < count($this->frames); $i++) { + $raw .= ' ' . $this->frames[$i]->getText(); + } + return trim($raw); + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getTransportError(); + } + return $this->frameset->getTransportError(); + } + + /** + * Request method used to fetch this frame. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getMethod(); + } + return $this->frameset->getMethod(); + } + + /** + * Original resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + if (is_integer($this->focus)) { + $url = $this->frames[$this->focus]->getUrl(); + $url->setTarget($this->getPublicNameFromIndex($this->focus)); + } else { + $url = $this->frameset->getUrl(); + } + return $url; + } + + /** + * Page base URL. + * @return SimpleUrl Current url. + * @access public + */ + function getBaseUrl() { + if (is_integer($this->focus)) { + $url = $this->frames[$this->focus]->getBaseUrl(); + } else { + $url = $this->frameset->getBaseUrl(); + } + return $url; + } + + /** + * Expands expandomatic URLs into fully qualified + * URLs for the frameset page. + * @param SimpleUrl $url Relative URL. + * @return SimpleUrl Absolute URL. + * @access public + */ + function expandUrl($url) { + return $this->frameset->expandUrl($url); + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRequestData(); + } + return $this->frameset->getRequestData(); + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getMimeType(); + } + return $this->frameset->getMimeType(); + } + + /** + * Accessor for last response code. + * @return integer Last HTTP response code received. + * @access public + */ + function getResponseCode() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getResponseCode(); + } + return $this->frameset->getResponseCode(); + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getAuthentication(); + } + return $this->frameset->getAuthentication(); + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRealm(); + } + return $this->frameset->getRealm(); + } + + /** + * Accessor for outgoing header information. + * @return string Header block. + * @access public + */ + function getRequest() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRequest(); + } + return $this->frameset->getRequest(); + } + + /** + * Accessor for raw header information. + * @return string Header block. + * @access public + */ + function getHeaders() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getHeaders(); + } + return $this->frameset->getHeaders(); + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + return $this->frameset->getTitle(); + } + + /** + * Accessor for a list of all fixed links. + * @return array List of urls as strings. + * @access public + */ + function getUrls() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getUrls(); + } + $urls = array(); + foreach ($this->frames as $frame) { + $urls = array_merge($urls, $frame->getUrls()); + } + return array_values(array_unique($urls)); + } + + /** + * Accessor for URLs by the link label. Label will match + * regardess of whitespace issues and case. + * @param string $label Text of link. + * @return array List of links with that label. + * @access public + */ + function getUrlsByLabel($label) { + if (is_integer($this->focus)) { + return $this->tagUrlsWithFrame( + $this->frames[$this->focus]->getUrlsByLabel($label), + $this->focus); + } + $urls = array(); + foreach ($this->frames as $index => $frame) { + $urls = array_merge( + $urls, + $this->tagUrlsWithFrame( + $frame->getUrlsByLabel($label), + $index)); + } + return $urls; + } + + /** + * Accessor for a URL by the id attribute. If in a frameset + * then the first link found with that ID attribute is + * returned only. Focus on a frame if you want one from + * a specific part of the frameset. + * @param string $id Id attribute of link. + * @return string URL with that id. + * @access public + */ + function getUrlById($id) { + foreach ($this->frames as $index => $frame) { + if ($url = $frame->getUrlById($id)) { + if (! $url->gettarget()) { + $url->setTarget($this->getPublicNameFromIndex($index)); + } + return $url; + } + } + return false; + } + + /** + * Attaches the intended frame index to a list of URLs. + * @param array $urls List of SimpleUrls. + * @param string $frame Name of frame or index. + * @return array List of tagged URLs. + * @access private + */ + protected function tagUrlsWithFrame($urls, $frame) { + $tagged = array(); + foreach ($urls as $url) { + if (! $url->getTarget()) { + $url->setTarget($this->getPublicNameFromIndex($frame)); + } + $tagged[] = $url; + } + return $tagged; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. + * @param SimpleSelector $selector Button finder. + * @return SimpleForm Form object containing + * the button. + * @access public + */ + function getFormBySubmit($selector) { + return $this->findForm('getFormBySubmit', $selector); + } + + /** + * Finds a held form by image using a selector. + * Will only search correctly built forms. The first + * form found either within the focused frame, or + * across frames, will be the one returned. + * @param SimpleSelector $selector Image finder. + * @return SimpleForm Form object containing + * the image. + * @access public + */ + function getFormByImage($selector) { + return $this->findForm('getFormByImage', $selector); + } + + /** + * Finds a held form by the form ID. A way of + * identifying a specific form when we have control + * of the HTML code. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $id Form label. + * @return SimpleForm Form object containing the matching ID. + * @access public + */ + function getFormById($id) { + return $this->findForm('getFormById', $id); + } + + /** + * General form finder. Will search all the frames or + * just the one in focus. + * @param string $method Method to use to find in a page. + * @param string $attribute Label, name or ID. + * @return SimpleForm Form object containing the matching ID. + * @access private + */ + protected function findForm($method, $attribute) { + if (is_integer($this->focus)) { + return $this->findFormInFrame( + $this->frames[$this->focus], + $this->focus, + $method, + $attribute); + } + for ($i = 0; $i < count($this->frames); $i++) { + $form = $this->findFormInFrame( + $this->frames[$i], + $i, + $method, + $attribute); + if ($form) { + return $form; + } + } + $null = null; + return $null; + } + + /** + * Finds a form in a page using a form finding method. Will + * also tag the form with the frame name it belongs in. + * @param SimplePage $page Page content of frame. + * @param integer $index Internal frame representation. + * @param string $method Method to use to find in a page. + * @param string $attribute Label, name or ID. + * @return SimpleForm Form object containing the matching ID. + * @access private + */ + protected function findFormInFrame($page, $index, $method, $attribute) { + $form = $this->frames[$index]->$method($attribute); + if (isset($form)) { + $form->setDefaultTarget($this->getPublicNameFromIndex($index)); + } + return $form; + } + + /** + * Sets a field on each form in which the field is + * available. + * @param SimpleSelector $selector Field finder. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setField($selector, $value) { + if (is_integer($this->focus)) { + $this->frames[$this->focus]->setField($selector, $value); + } else { + for ($i = 0; $i < count($this->frames); $i++) { + $this->frames[$i]->setField($selector, $value); + } + } + } + + /** + * Accessor for a form element value within a page. + * @param SimpleSelector $selector Field finder. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($selector) { + for ($i = 0; $i < count($this->frames); $i++) { + $value = $this->frames[$i]->getField($selector); + if (isset($value)) { + return $value; + } + } + return null; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/http.php b/lib/Swift/test-suite/lib/simpletest/http.php new file mode 100644 index 0000000..15b555c --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/http.php @@ -0,0 +1,628 @@ +url = $url; + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access protected + */ + function getUrl() { + return $this->url; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @return string Request line content. + * @access protected + */ + protected function getRequestLine($method) { + return $method . ' ' . $this->url->getPath() . + $this->url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @return string Host line content. + * @access protected + */ + protected function getHostLine() { + $line = 'Host: ' . $this->url->getHost(); + if ($this->url->getPort()) { + $line .= ':' . $this->url->getPort(); + } + return $line; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function createConnection($method, $timeout) { + $default_port = ('https' == $this->url->getScheme()) ? 443 : 80; + $socket = $this->createSocket( + $this->url->getScheme() ? $this->url->getScheme() : 'http', + $this->url->getHost(), + $this->url->getPort() ? $this->url->getPort() : $default_port, + $timeout); + if (! $socket->isError()) { + $socket->write($this->getRequestLine($method) . "\r\n"); + $socket->write($this->getHostLine() . "\r\n"); + $socket->write("Connection: close\r\n"); + } + return $socket; + } + + /** + * Factory for socket. + * @param string $scheme Protocol to use. + * @param string $host Hostname to connect to. + * @param integer $port Remote port. + * @param integer $timeout Connection timeout. + * @return SimpleSocket/SimpleSecureSocket New socket. + * @access protected + */ + protected function createSocket($scheme, $host, $port, $timeout) { + if (in_array($scheme, array('file'))) { + return new SimpleFileSocket($this->url); + } elseif (in_array($scheme, array('https'))) { + return new SimpleSecureSocket($host, $port, $timeout); + } else { + return new SimpleSocket($host, $port, $timeout); + } + } +} + +/** + * Creates HTTP headers for the end point of + * a HTTP request via a proxy server. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleProxyRoute extends SimpleRoute { + private $proxy; + private $username; + private $password; + + /** + * Stashes the proxy address. + * @param SimpleUrl $url URL as object. + * @param string $proxy Proxy URL. + * @param string $username Username for autentication. + * @param string $password Password for autentication. + * @access public + */ + function __construct($url, $proxy, $username = false, $password = false) { + parent::__construct($url); + $this->proxy = $proxy; + $this->username = $username; + $this->password = $password; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @param SimpleUrl $url URL as object. + * @return string Request line content. + * @access protected + */ + function getRequestLine($method) { + $url = $this->getUrl(); + $scheme = $url->getScheme() ? $url->getScheme() : 'http'; + $port = $url->getPort() ? ':' . $url->getPort() : ''; + return $method . ' ' . $scheme . '://' . $url->getHost() . $port . + $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @param SimpleUrl $url URL as object. + * @return string Host line content. + * @access protected + */ + function getHostLine() { + $host = 'Host: ' . $this->proxy->getHost(); + $port = $this->proxy->getPort() ? $this->proxy->getPort() : 8080; + return "$host:$port"; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function createConnection($method, $timeout) { + $socket = $this->createSocket( + $this->proxy->getScheme() ? $this->proxy->getScheme() : 'http', + $this->proxy->getHost(), + $this->proxy->getPort() ? $this->proxy->getPort() : 8080, + $timeout); + if ($socket->isError()) { + return $socket; + } + $socket->write($this->getRequestLine($method) . "\r\n"); + $socket->write($this->getHostLine() . "\r\n"); + if ($this->username && $this->password) { + $socket->write('Proxy-Authorization: Basic ' . + base64_encode($this->username . ':' . $this->password) . + "\r\n"); + } + $socket->write("Connection: close\r\n"); + return $socket; + } +} + +/** + * HTTP request for a web page. Factory for + * HttpResponse object. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpRequest { + private $route; + private $encoding; + private $headers; + private $cookies; + + /** + * Builds the socket request from the different pieces. + * These include proxy information, URL, cookies, headers, + * request method and choice of encoding. + * @param SimpleRoute $route Request route. + * @param SimpleFormEncoding $encoding Content to send with + * request. + * @access public + */ + function __construct($route, $encoding) { + $this->route = $route; + $this->encoding = $encoding; + $this->headers = array(); + $this->cookies = array(); + } + + /** + * Dispatches the content to the route's socket. + * @param integer $timeout Connection timeout. + * @return SimpleHttpResponse A response which may only have + * an error, but hopefully has a + * complete web page. + * @access public + */ + function fetch($timeout) { + $socket = $this->route->createConnection($this->encoding->getMethod(), $timeout); + if (! $socket->isError()) { + $this->dispatchRequest($socket, $this->encoding); + } + return $this->createResponse($socket); + } + + /** + * Sends the headers. + * @param SimpleSocket $socket Open socket. + * @param string $method HTTP request method, + * usually GET. + * @param SimpleFormEncoding $encoding Content to send with request. + * @access private + */ + protected function dispatchRequest($socket, $encoding) { + foreach ($this->headers as $header_line) { + $socket->write($header_line . "\r\n"); + } + if (count($this->cookies) > 0) { + $socket->write("Cookie: " . implode(";", $this->cookies) . "\r\n"); + } + $encoding->writeHeadersTo($socket); + $socket->write("\r\n"); + $encoding->writeTo($socket); + } + + /** + * Adds a header line to the request. + * @param string $header_line Text of full header line. + * @access public + */ + function addHeaderLine($header_line) { + $this->headers[] = $header_line; + } + + /** + * Reads all the relevant cookies from the + * cookie jar. + * @param SimpleCookieJar $jar Jar to read + * @param SimpleUrl $url Url to use for scope. + * @access public + */ + function readCookiesFromJar($jar, $url) { + $this->cookies = $jar->selectAsPairs($url); + } + + /** + * Wraps the socket in a response parser. + * @param SimpleSocket $socket Responding socket. + * @return SimpleHttpResponse Parsed response object. + * @access protected + */ + protected function createResponse($socket) { + $response = new SimpleHttpResponse( + $socket, + $this->route->getUrl(), + $this->encoding); + $socket->close(); + return $response; + } +} + +/** + * Collection of header lines in the response. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpHeaders { + private $raw_headers; + private $response_code; + private $http_version; + private $mime_type; + private $location; + private $cookies; + private $authentication; + private $realm; + + /** + * Parses the incoming header block. + * @param string $headers Header block. + * @access public + */ + function __construct($headers) { + $this->raw_headers = $headers; + $this->response_code = false; + $this->http_version = false; + $this->mime_type = ''; + $this->location = false; + $this->cookies = array(); + $this->authentication = false; + $this->realm = false; + foreach (split("\r\n", $headers) as $header_line) { + $this->parseHeaderLine($header_line); + } + } + + /** + * Accessor for parsed HTTP protocol version. + * @return integer HTTP error code. + * @access public + */ + function getHttpVersion() { + return $this->http_version; + } + + /** + * Accessor for raw header block. + * @return string All headers as raw string. + * @access public + */ + function getRaw() { + return $this->raw_headers; + } + + /** + * Accessor for parsed HTTP error code. + * @return integer HTTP error code. + * @access public + */ + function getResponseCode() { + return (integer)$this->response_code; + } + + /** + * Returns the redirected URL or false if + * no redirection. + * @return string URL or false for none. + * @access public + */ + function getLocation() { + return $this->location; + } + + /** + * Test to see if the response is a valid redirect. + * @return boolean True if valid redirect. + * @access public + */ + function isRedirect() { + return in_array($this->response_code, array(301, 302, 303, 307)) && + (boolean)$this->getLocation(); + } + + /** + * Test to see if the response is an authentication + * challenge. + * @return boolean True if challenge. + * @access public + */ + function isChallenge() { + return ($this->response_code == 401) && + (boolean)$this->authentication && + (boolean)$this->realm; + } + + /** + * Accessor for MIME type header information. + * @return string MIME type. + * @access public + */ + function getMimeType() { + return $this->mime_type; + } + + /** + * Accessor for authentication type. + * @return string Type. + * @access public + */ + function getAuthentication() { + return $this->authentication; + } + + /** + * Accessor for security realm. + * @return string Realm. + * @access public + */ + function getRealm() { + return $this->realm; + } + + /** + * Writes new cookies to the cookie jar. + * @param SimpleCookieJar $jar Jar to write to. + * @param SimpleUrl $url Host and path to write under. + * @access public + */ + function writeCookiesToJar($jar, $url) { + foreach ($this->cookies as $cookie) { + $jar->setCookie( + $cookie->getName(), + $cookie->getValue(), + $url->getHost(), + $cookie->getPath(), + $cookie->getExpiry()); + } + } + + /** + * Called on each header line to accumulate the held + * data within the class. + * @param string $header_line One line of header. + * @access protected + */ + protected function parseHeaderLine($header_line) { + if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) { + $this->http_version = $matches[1]; + $this->response_code = $matches[2]; + } + if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) { + $this->mime_type = trim($matches[1]); + } + if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) { + $this->location = trim($matches[1]); + } + if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) { + $this->cookies[] = $this->parseCookie($matches[1]); + } + if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) { + $this->authentication = $matches[1]; + $this->realm = trim($matches[2]); + } + } + + /** + * Parse the Set-cookie content. + * @param string $cookie_line Text after "Set-cookie:" + * @return SimpleCookie New cookie object. + * @access private + */ + protected function parseCookie($cookie_line) { + $parts = split(";", $cookie_line); + $cookie = array(); + preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie); + foreach ($parts as $part) { + if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) { + $cookie[$matches[1]] = trim($matches[2]); + } + } + return new SimpleCookie( + $cookie[1], + trim($cookie[2]), + isset($cookie["path"]) ? $cookie["path"] : "", + isset($cookie["expires"]) ? $cookie["expires"] : false); + } +} + +/** + * Basic HTTP response. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpResponse extends SimpleStickyError { + private $url; + private $encoding; + private $sent; + private $content; + private $headers; + + /** + * Constructor. Reads and parses the incoming + * content and headers. + * @param SimpleSocket $socket Network connection to fetch + * response text from. + * @param SimpleUrl $url Resource name. + * @param mixed $encoding Record of content sent. + * @access public + */ + function __construct($socket, $url, $encoding) { + parent::__construct(); + $this->url = $url; + $this->encoding = $encoding; + $this->sent = $socket->getSent(); + $this->content = false; + $raw = $this->readAll($socket); + if ($socket->isError()) { + $this->setError('Error reading socket [' . $socket->getError() . ']'); + return; + } + $this->parse($raw); + } + + /** + * Splits up the headers and the rest of the content. + * @param string $raw Content to parse. + * @access private + */ + protected function parse($raw) { + if (! $raw) { + $this->setError('Nothing fetched'); + $this->headers = new SimpleHttpHeaders(''); + } elseif ('file' == $this->url->getScheme()) { + $this->headers = new SimpleHttpHeaders(''); + $this->content = $raw; + } elseif (! strstr($raw, "\r\n\r\n")) { + $this->setError('Could not split headers from content'); + $this->headers = new SimpleHttpHeaders($raw); + } else { + list($headers, $this->content) = split("\r\n\r\n", $raw, 2); + $this->headers = new SimpleHttpHeaders($headers); + } + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->encoding->getMethod(); + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->url; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->encoding; + } + + /** + * Raw request that was sent down the wire. + * @return string Bytes actually sent. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Accessor for the content after the last + * header line. + * @return string All content. + * @access public + */ + function getContent() { + return $this->content; + } + + /** + * Accessor for header block. The response is the + * combination of this and the content. + * @return SimpleHeaders Wrapped header block. + * @access public + */ + function getHeaders() { + return $this->headers; + } + + /** + * Accessor for any new cookies. + * @return array List of new cookies. + * @access public + */ + function getNewCookies() { + return $this->headers->getNewCookies(); + } + + /** + * Reads the whole of the socket output into a + * single string. + * @param SimpleSocket $socket Unread socket. + * @return string Raw output if successful + * else false. + * @access private + */ + protected function readAll($socket) { + $all = ''; + while (! $this->isLastPacket($next = $socket->read())) { + $all .= $next; + } + return $all; + } + + /** + * Test to see if the packet from the socket is the + * last one. + * @param string $packet Chunk to interpret. + * @return boolean True if empty or EOF. + * @access private + */ + protected function isLastPacket($packet) { + if (is_string($packet)) { + return $packet === ''; + } + return ! $packet; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/invoker.php b/lib/Swift/test-suite/lib/simpletest/invoker.php new file mode 100644 index 0000000..ee31034 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/invoker.php @@ -0,0 +1,139 @@ +test_case = $test_case; + } + + /** + * Accessor for test case being run. + * @return SimpleTestCase Test case. + * @access public + */ + function getTestCase() { + return $this->test_case; + } + + /** + * Runs test level set up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function before($method) { + $this->test_case->before($method); + } + + /** + * Invokes a test method and buffered with setUp() + * and tearDown() calls. + * @param string $method Test method to call. + * @access public + */ + function invoke($method) { + $this->test_case->setUp(); + $this->test_case->$method(); + $this->test_case->tearDown(); + } + + /** + * Runs test level clean up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function after($method) { + $this->test_case->after($method); + } +} + +/** + * Do nothing decorator. Just passes the invocation + * straight through. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleInvokerDecorator { + private $invoker; + + /** + * Stores the invoker to wrap. + * @param SimpleInvoker $invoker Test method runner. + */ + function __construct($invoker) { + $this->invoker = $invoker; + } + + /** + * Accessor for test case being run. + * @return SimpleTestCase Test case. + * @access public + */ + function getTestCase() { + return $this->invoker->getTestCase(); + } + + /** + * Runs test level set up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function before($method) { + $this->invoker->before($method); + } + + /** + * Invokes a test method and buffered with setUp() + * and tearDown() calls. + * @param string $method Test method to call. + * @access public + */ + function invoke($method) { + $this->invoker->invoke($method); + } + + /** + * Runs test level clean up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function after($method) { + $this->invoker->after($method); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/mock_objects.php b/lib/Swift/test-suite/lib/simpletest/mock_objects.php new file mode 100644 index 0000000..c3ed38b --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/mock_objects.php @@ -0,0 +1,1630 @@ +expected = $expected; + } + + /** + * Tests the assertion. True if correct. + * @param array $parameters Comparison values. + * @return boolean True if correct. + * @access public + */ + function test($parameters) { + if (! is_array($this->expected)) { + return true; + } + if (count($this->expected) != count($parameters)) { + return false; + } + for ($i = 0; $i < count($this->expected); $i++) { + if (! $this->testParameter($parameters[$i], $this->expected[$i])) { + return false; + } + } + return true; + } + + /** + * Tests an individual parameter. + * @param mixed $parameter Value to test. + * @param mixed $expected Comparison value. + * @return boolean True if expectation + * fulfilled. + * @access private + */ + protected function testParameter($parameter, $expected) { + $comparison = $this->coerceToExpectation($expected); + return $comparison->test($parameter); + } + + /** + * Returns a human readable test message. + * @param array $comparison Incoming parameter list. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($parameters) { + if ($this->test($parameters)) { + return "Expectation of " . count($this->expected) . + " arguments of [" . $this->renderArguments($this->expected) . + "] is correct"; + } else { + return $this->describeDifference($this->expected, $parameters); + } + } + + /** + * Message to display if expectation differs from + * the parameters actually received. + * @param array $expected Expected parameters as list. + * @param array $parameters Actual parameters received. + * @return string Description of difference. + * @access private + */ + protected function describeDifference($expected, $parameters) { + if (count($expected) != count($parameters)) { + return "Expected " . count($expected) . + " arguments of [" . $this->renderArguments($expected) . + "] but got " . count($parameters) . + " arguments of [" . $this->renderArguments($parameters) . "]"; + } + $messages = array(); + for ($i = 0; $i < count($expected); $i++) { + $comparison = $this->coerceToExpectation($expected[$i]); + if (! $comparison->test($parameters[$i])) { + $messages[] = "parameter " . ($i + 1) . " with [" . + $comparison->overlayMessage($parameters[$i], $this->getDumper()) . "]"; + } + } + return "Parameter expectation differs at " . implode(" and ", $messages); + } + + /** + * Creates an identical expectation if the + * object/value is not already some type + * of expectation. + * @param mixed $expected Expected value. + * @return SimpleExpectation Expectation object. + * @access private + */ + protected function coerceToExpectation($expected) { + if (SimpleExpectation::isExpectation($expected)) { + return $expected; + } + return new IdenticalExpectation($expected); + } + + /** + * Renders the argument list as a string for + * messages. + * @param array $args Incoming arguments. + * @return string Simple description of type and value. + * @access private + */ + protected function renderArguments($args) { + $descriptions = array(); + if (is_array($args)) { + foreach ($args as $arg) { + $dumper = new SimpleDumper(); + $descriptions[] = $dumper->describeValue($arg); + } + } + return implode(', ', $descriptions); + } +} + +/** + * Confirms that the number of calls on a method is as expected. + * @package SimpleTest + * @subpackage MockObjects + */ +class CallCountExpectation extends SimpleExpectation { + private $method; + private $count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Expected number of calls. + * @param string $message Custom error message. + */ + function __construct($method, $count, $message = '%s') { + $this->method = $method; + $this->count = $count; + parent::__construct($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if expected. + * @access public + */ + function test($compare) { + return ($this->count == $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Expected call count for [' . $this->method . + '] was [' . $this->count . + '] got [' . $compare . ']'; + } +} + +/** + * Confirms that the number of calls on a method is as expected. + * @package SimpleTest + * @subpackage MockObjects + */ +class MinimumCallCountExpectation extends SimpleExpectation { + private $method; + private $count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Minimum number of calls. + * @param string $message Custom error message. + */ + function __construct($method, $count, $message = '%s') { + $this->method = $method; + $this->count = $count; + parent::__construct($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if enough. + * @access public + */ + function test($compare) { + return ($this->count <= $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Minimum call count for [' . $this->method . + '] was [' . $this->count . + '] got [' . $compare . ']'; + } +} + +/** + * Confirms that the number of calls on a method is as expected. + * @package SimpleTest + * @subpackage MockObjects + */ +class MaximumCallCountExpectation extends SimpleExpectation { + private $method; + private $count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Minimum number of calls. + * @param string $message Custom error message. + */ + function __construct($method, $count, $message = '%s') { + $this->method = $method; + $this->count = $count; + parent::__construct($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if not over. + * @access public + */ + function test($compare) { + return ($this->count >= $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Maximum call count for [' . $this->method . + '] was [' . $this->count . + '] got [' . $compare . ']'; + } +} + +/** + * Retrieves method actions by searching the + * parameter lists until an expected match is found. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleSignatureMap { + private $map; + + /** + * Creates an empty call map. + * @access public + */ + function __construct() { + $this->map = array(); + } + + /** + * Stashes a reference against a method call. + * @param array $parameters Array of arguments (including wildcards). + * @param mixed $action Reference placed in the map. + * @access public + */ + function add($parameters, $action) { + $place = count($this->map); + $this->map[$place] = array(); + $this->map[$place]['params'] = new ParametersExpectation($parameters); + $this->map[$place]['content'] = $action; + } + + /** + * Searches the call list for a matching parameter + * set. Returned by reference. + * @param array $parameters Parameters to search by + * without wildcards. + * @return object Object held in the first matching + * slot, otherwise null. + * @access public + */ + function &findFirstAction($parameters) { + $slot = $this->findFirstSlot($parameters); + if (isset($slot) && isset($slot['content'])) { + return $slot['content']; + } + $null = null; + return $null; + } + + /** + * Searches the call list for a matching parameter + * set. True if successful. + * @param array $parameters Parameters to search by + * without wildcards. + * @return boolean True if a match is present. + * @access public + */ + function isMatch($parameters) { + return ($this->findFirstSlot($parameters) != null); + } + + /** + * Compares the incoming parameters with the + * internal expectation. Uses the incoming $test + * to dispatch the test message. + * @param SimpleTestCase $test Test to dispatch to. + * @param array $parameters The actual calling arguments. + * @param string $message The message to overlay. + * @access public + */ + function test($test, $parameters, $message) { + } + + /** + * Searches the map for a matching item. + * @param array $parameters Parameters to search by + * without wildcards. + * @return array Reference to slot or null. + * @access private + */ + function &findFirstSlot($parameters) { + $count = count($this->map); + for ($i = 0; $i < $count; $i++) { + if ($this->map[$i]["params"]->test($parameters)) { + return $this->map[$i]; + } + } + $null = null; + return $null; + } +} + +/** + * Allows setting of actions against call signatures either + * at a specific time, or always. Specific time settings + * trump lasting ones, otherwise the most recently added + * will mask an earlier match. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleCallSchedule { + private $wildcard = MOCK_ANYTHING; + private $always; + private $at; + + /** + * Sets up an empty response schedule. + * Creates an empty call map. + */ + function __construct() { + $this->always = array(); + $this->at = array(); + } + + /** + * Stores an action against a signature that + * will always fire unless masked by a time + * specific one. + * @param string $method Method name. + * @param array $args Calling parameters. + * @param SimpleAction $action Actually simpleByValue, etc. + * @access public + */ + function register($method, $args, $action) { + $args = $this->replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->always[$method])) { + $this->always[$method] = new SimpleSignatureMap(); + } + $this->always[$method]->add($args, $action); + } + + /** + * Stores an action against a signature that + * will fire at a specific time in the future. + * @param integer $step delay of calls to this method, + * 0 is next. + * @param string $method Method name. + * @param array $args Calling parameters. + * @param SimpleAction $action Actually SimpleByValue, etc. + * @access public + */ + function registerAt($step, $method, $args, $action) { + $args = $this->replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->at[$method])) { + $this->at[$method] = array(); + } + if (! isset($this->at[$method][$step])) { + $this->at[$method][$step] = new SimpleSignatureMap(); + } + $this->at[$method][$step]->add($args, $action); + } + + /** + * Sets up an expectation on the argument list. + * @param string $method Method to test. + * @param array $args Bare arguments or list of + * expectation objects. + * @param string $message Failure message. + */ + function expectArguments($method, $args, $message) { + $args = $this->replaceWildcards($args); + $message .= Mock::getExpectationLine(); + $this->expected_args[strtolower($method)] = + new ParametersExpectation($args, $message); + + } + + /** + * Actually carry out the action stored previously, + * if the parameters match. + * @param integer $step Time of call. + * @param string $method Method name. + * @param array $args The parameters making up the + * rest of the call. + * @return mixed The result of the action. + * @access public. + */ + function &respond($step, $method, $args) { + $method = strtolower($method); + if (isset($this->at[$method][$step])) { + if ($this->at[$method][$step]->isMatch($args)) { + $action = $this->at[$method][$step]->findFirstAction($args); + if (isset($action)) { + return $action->act(); + } + } + } + if (isset($this->always[$method])) { + $action = $this->always[$method]->findFirstAction($args); + if (isset($action)) { + return $action->act(); + } + } + $null = null; + return $null; + } + + /** + * Replaces wildcard matches with wildcard + * expectations in the argument list. + * @param array $args Raw argument list. + * @return array Argument list with + * expectations. + * @access private + */ + protected function replaceWildcards($args) { + if ($args === false) { + return false; + } + for ($i = 0; $i < count($args); $i++) { + if ($args[$i] === $this->wildcard) { + $args[$i] = new AnythingExpectation(); + } + } + return $args; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes a value for returning later. Follows usual + * PHP5 semantics of objects being returned by reference. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleReturn { + private $value; + + /** + * Stashes it for later. + * @param mixed $value You need to clone objects + * if you want copy semantics + * for these. + * @access public + */ + function __construct($value) { + $this->value = $value; + } + + /** + * Returns the value stored earlier. + * @return mixed Whatever was stashed. + * @access public + */ + function act() { + return $this->value; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes a reference for returning later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleByReference { + private $reference; + + /** + * Stashes it for later. + * @param mixed $reference Actual PHP4 style reference. + * @access public + */ + function __construct(&$reference) { + $this->reference = &$reference; + } + + /** + * Returns the reference stored earlier. + * @return mixed Whatever was stashed. + * @access public + */ + function &act() { + return $this->reference; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes a value for returning later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleByValue { + private $value; + + /** + * Stashes it for later. + * @param mixed $value You need to clone objects + * if you want copy semantics + * for these. + * @access public + */ + function __construct($value) { + $this->value = $value; + } + + /** + * Returns the value stored earlier. + * @return mixed Whatever was stashed. + * @access public + */ + function &act() { + $dummy = $this->value; + return $dummy; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes an exception for throwing later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleThrower { + private $exception; + + /** + * Stashes it for later. + * @param Exception $exception The exception object to throw. + * @access public + */ + function __construct($exception) { + $this->exception = $exception; + } + + /** + * Throws the exceptins stashed earlier. + * @access public + */ + function act() { + throw $this->exception; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes an error for emitting later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleErrorThrower { + private $error; + private $severity; + + /** + * Stashes an error to throw later. + * @param string $error Error message. + * @param integer $severity PHP error constant, e.g E_USER_ERROR. + * @access public + */ + function __construct($error, $severity) { + $this->error = $error; + $this->severity = $severity; + } + + /** + * Triggers the stashed error. + * @access public + */ + function &act() { + trigger_error($this->error, $this->severity); + $null = null; + return $null; + } +} + +/** + * A base class or delegate that extends an + * empty collection of methods that can have their + * return values set and expectations made of the + * calls upon them. The mock will assert the + * expectations against it's attached test case in + * addition to the server stub behaviour or returning + * preprogrammed responses. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleMock { + private $actions; + private $expectations; + private $wildcard = MOCK_ANYTHING; + private $is_strict = true; + private $call_counts; + private $expected_counts; + private $max_counts; + private $expected_args; + private $expected_args_at; + + /** + * Creates an empty action list and expectation list. + * All call counts are set to zero. + * @access public + */ + function SimpleMock() { + $this->actions = new SimpleCallSchedule(); + $this->expectations = new SimpleCallSchedule(); + $this->call_counts = array(); + $this->expected_counts = array(); + $this->max_counts = array(); + $this->expected_args = array(); + $this->expected_args_at = array(); + $this->getCurrentTestCase()->tell($this); + } + + /** + * Disables a name check when setting expectations. + * This hack is needed for the partial mocks. + * @access public + */ + function disableExpectationNameChecks() { + $this->is_strict = false; + } + + /** + * Finds currently running test. + * @return SimpeTestCase Current test case. + * @access protected + */ + protected function getCurrentTestCase() { + return SimpleTest::getContext()->getTest(); + } + + /** + * Die if bad arguments array is passed. + * @param mixed $args The arguments value to be checked. + * @param string $task Description of task attempt. + * @return boolean Valid arguments + * @access private + */ + protected function checkArgumentsIsArray($args, $task) { + if (! is_array($args)) { + trigger_error( + "Cannot $task as \$args parameter is not an array", + E_USER_ERROR); + } + } + + /** + * Triggers a PHP error if the method is not part + * of this object. + * @param string $method Name of method. + * @param string $task Description of task attempt. + * @access protected + */ + protected function dieOnNoMethod($method, $task) { + if ($this->is_strict && ! method_exists($this, $method)) { + trigger_error( + "Cannot $task as no ${method}() in class " . get_class($this), + E_USER_ERROR); + } + } + + /** + * Replaces wildcard matches with wildcard + * expectations in the argument list. + * @param array $args Raw argument list. + * @return array Argument list with + * expectations. + * @access private + */ + function replaceWildcards($args) { + if ($args === false) { + return false; + } + for ($i = 0; $i < count($args); $i++) { + if ($args[$i] === $this->wildcard) { + $args[$i] = new AnythingExpectation(); + } + } + return $args; + } + + /** + * Adds one to the call count of a method. + * @param string $method Method called. + * @param array $args Arguments as an array. + * @access protected + */ + protected function addCall($method, $args) { + if (! isset($this->call_counts[$method])) { + $this->call_counts[$method] = 0; + } + $this->call_counts[$method]++; + } + + /** + * Fetches the call count of a method so far. + * @param string $method Method name called. + * @return integer Number of calls so far. + * @access public + */ + function getCallCount($method) { + $this->dieOnNoMethod($method, "get call count"); + $method = strtolower($method); + if (! isset($this->call_counts[$method])) { + return 0; + } + return $this->call_counts[$method]; + } + + /** + * Sets a return for a parameter list that will + * be passed on by all calls to this method that match. + * @param string $method Method name. + * @param mixed $value Result of call by value/handle. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function returns($method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return"); + $this->actions->register($method, $args, new SimpleReturn($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $value Result of call passed. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function returnsAt($timing, $method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return value sequence"); + $this->actions->registerAt($timing, $method, $args, new SimpleReturn($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed by value for all calls to this method. + * @param string $method Method name. + * @param mixed $value Result of call passed by value. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnValue($method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return value"); + $this->actions->register($method, $args, new SimpleByValue($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed by value only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $value Result of call passed by value. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnValueAt($timing, $method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return value sequence"); + $this->actions->registerAt($timing, $method, $args, new SimpleByValue($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed by reference for all calls. + * @param string $method Method name. + * @param mixed $reference Result of the call will be this object. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnReference($method, &$reference, $args = false) { + $this->dieOnNoMethod($method, "set return reference"); + $this->actions->register($method, $args, new SimpleByReference($reference)); + } + + /** + * Sets a return for a parameter list that will + * be passed by value only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $reference Result of the call will be this object. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnReferenceAt($timing, $method, &$reference, $args = false) { + $this->dieOnNoMethod($method, "set return reference sequence"); + $this->actions->registerAt($timing, $method, $args, new SimpleByReference($reference)); + } + + /** + * Sets up an expected call with a set of + * expected parameters in that call. All + * calls will be compared to these expectations + * regardless of when the call is made. + * @param string $method Method call to test. + * @param array $args Expected parameters for the call + * including wildcards. + * @param string $message Overridden message. + * @access public + */ + function expect($method, $args, $message = '%s') { + $this->dieOnNoMethod($method, 'set expected arguments'); + $this->checkArgumentsIsArray($args, 'set expected arguments'); + $this->expectations->expectArguments($method, $args, $message); + $args = $this->replaceWildcards($args); + $message .= Mock::getExpectationLine(); + $this->expected_args[strtolower($method)] = + new ParametersExpectation($args, $message); + } + + /** + * Sets up an expected call with a set of + * expected parameters in that call. The + * expected call count will be adjusted if it + * is set too low to reach this call. + * @param integer $timing Number of calls in the future at + * which to test. Next call is 0. + * @param string $method Method call to test. + * @param array $args Expected parameters for the call + * including wildcards. + * @param string $message Overridden message. + * @access public + */ + function expectAt($timing, $method, $args, $message = '%s') { + $this->dieOnNoMethod($method, 'set expected arguments at time'); + $this->checkArgumentsIsArray($args, 'set expected arguments at time'); + $args = $this->replaceWildcards($args); + if (! isset($this->expected_args_at[$timing])) { + $this->expected_args_at[$timing] = array(); + } + $method = strtolower($method); + $message .= Mock::getExpectationLine(); + $this->expected_args_at[$timing][$method] = + new ParametersExpectation($args, $message); + } + + /** + * Sets an expectation for the number of times + * a method will be called. The tally method + * is used to check this. + * @param string $method Method call to test. + * @param integer $count Number of times it should + * have been called at tally. + * @param string $message Overridden message. + * @access public + */ + function expectCallCount($method, $count, $message = '%s') { + $this->dieOnNoMethod($method, 'set expected call count'); + $message .= Mock::getExpectationLine(); + $this->expected_counts[strtolower($method)] = + new CallCountExpectation($method, $count, $message); + } + + /** + * Sets the number of times a method may be called + * before a test failure is triggered. + * @param string $method Method call to test. + * @param integer $count Most number of times it should + * have been called. + * @param string $message Overridden message. + * @access public + */ + function expectMaximumCallCount($method, $count, $message = '%s') { + $this->dieOnNoMethod($method, 'set maximum call count'); + $message .= Mock::getExpectationLine(); + $this->max_counts[strtolower($method)] = + new MaximumCallCountExpectation($method, $count, $message); + } + + /** + * Sets the number of times to call a method to prevent + * a failure on the tally. + * @param string $method Method call to test. + * @param integer $count Least number of times it should + * have been called. + * @param string $message Overridden message. + * @access public + */ + function expectMinimumCallCount($method, $count, $message = '%s') { + $this->dieOnNoMethod($method, 'set minimum call count'); + $message .= Mock::getExpectationLine(); + $this->expected_counts[strtolower($method)] = + new MinimumCallCountExpectation($method, $count, $message); + } + + /** + * Convenience method for barring a method + * call. + * @param string $method Method call to ban. + * @param string $message Overridden message. + * @access public + */ + function expectNever($method, $message = '%s') { + $this->expectMaximumCallCount($method, 0, $message); + } + + /** + * Convenience method for a single method + * call. + * @param string $method Method call to track. + * @param array $args Expected argument list or + * false for any arguments. + * @param string $message Overridden message. + * @access public + */ + function expectOnce($method, $args = false, $message = '%s') { + $this->expectCallCount($method, 1, $message); + if ($args !== false) { + $this->expect($method, $args, $message); + } + } + + /** + * Convenience method for requiring a method + * call. + * @param string $method Method call to track. + * @param array $args Expected argument list or + * false for any arguments. + * @param string $message Overridden message. + * @access public + */ + function expectAtLeastOnce($method, $args = false, $message = '%s') { + $this->expectMinimumCallCount($method, 1, $message); + if ($args !== false) { + $this->expect($method, $args, $message); + } + } + + /** + * Sets up a trigger to throw an exception upon the + * method call. + * @param string $method Method name to throw on. + */ + function throwOn($method, $exception = false, $args = false) { + $this->dieOnNoMethod($method, "throw on"); + $this->actions->register($method, $args, + new SimpleThrower($exception ? $exception : new Exception())); + } + + /** + * Sets up a trigger to throw an exception upon the + * method call. + */ + function throwAt($timing, $method, $exception = false, $args = false) { + $this->dieOnNoMethod($method, "throw at"); + $this->actions->registerAt($timing, $method, $args, + new SimpleThrower($exception ? $exception : new Exception())); + } + + /** + * Sets up a trigger to throw an error upon the + * method call. + */ + function errorOn($method, $error = 'A mock error', $args = false, $severity = E_USER_ERROR) { + $this->dieOnNoMethod($method, "error on"); + $this->actions->register($method, $args, new SimpleErrorThrower($error, $severity)); + } + + /** + * Sets up a trigger to throw an error upon the + * method call. + */ + function errorAt($timing, $method, $error = 'A mock error', $args = false, $severity = E_USER_ERROR) { + $this->dieOnNoMethod($method, "error at"); + $this->actions->registerAt($timing, $method, $args, new SimpleErrorThrower($error, $severity)); + } + + /** + * Receives event from unit test that the current + * test method has finished. Totals up the call + * counts and triggers a test assertion if a test + * is present for expected call counts. + * @param string $test_method Current method name. + * @param SimpleTestCase $test Test to send message to. + * @access public + */ + function atTestEnd($test_method, &$test) { + foreach ($this->expected_counts as $method => $expectation) { + $test->assert($expectation, $this->getCallCount($method)); + } + foreach ($this->max_counts as $method => $expectation) { + if ($expectation->test($this->getCallCount($method))) { + $test->assert($expectation, $this->getCallCount($method)); + } + } + } + + /** + * Returns the expected value for the method name + * and checks expectations. Will generate any + * test assertions as a result of expectations + * if there is a test present. + * @param string $method Name of method to simulate. + * @param array $args Arguments as an array. + * @return mixed Stored return. + * @access private + */ + function &invoke($method, $args) { + $method = strtolower($method); + $step = $this->getCallCount($method); + $this->addCall($method, $args); + $this->checkExpectations($method, $args, $step); + $was = $this->disableEStrict(); + try { + $result = &$this->emulateCall($method, $args, $step); + } catch (Exception $e) { + $this->restoreEStrict($was); + throw $e; + } + $this->restoreEStrict($was); + return $result; + } + + /** + * Finds the return value matching the incoming + * arguments. If there is no matching value found + * then an error is triggered. + * @param string $method Method name. + * @param array $args Calling arguments. + * @param integer $step Current position in the + * call history. + * @return mixed Stored return or other action. + * @access protected + */ + protected function &emulateCall($method, $args, $step) { + return $this->actions->respond($step, $method, $args); + } + + /** + * Tests the arguments against expectations. + * @param string $method Method to check. + * @param array $args Argument list to match. + * @param integer $timing The position of this call + * in the call history. + * @access private + */ + protected function checkExpectations($method, $args, $timing) { + $test = $this->getCurrentTestCase(); + if (isset($this->max_counts[$method])) { + if (! $this->max_counts[$method]->test($timing + 1)) { + $test->assert($this->max_counts[$method], $timing + 1); + } + } + if (isset($this->expected_args_at[$timing][$method])) { + $test->assert( + $this->expected_args_at[$timing][$method], + $args, + "Mock method [$method] at [$timing] -> %s"); + } elseif (isset($this->expected_args[$method])) { + $test->assert( + $this->expected_args[$method], + $args, + "Mock method [$method] -> %s"); + } + } + + private function disableEStrict() { + $was = error_reporting(); + error_reporting($was & ~E_STRICT); + return $was; + } + + private function restoreEStrict($was) { + error_reporting($was); + } +} + +/** + * Static methods only service class for code generation of + * mock objects. + * @package SimpleTest + * @subpackage MockObjects + */ +class Mock { + + /** + * Factory for mock object classes. + * @access public + */ + function __construct() { + trigger_error('Mock factory methods are static.'); + } + + /** + * Clones a class' interface and creates a mock version + * that can have return values and expectations set. + * @param string $class Class to clone. + * @param string $mock_class New class name. Default is + * the old name with "Mock" + * prepended. + * @param array $methods Additional methods to add beyond + * those in the cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet.sta + * @access public + */ + static function generate($class, $mock_class = false, $methods = false) { + $generator = new MockGenerator($class, $mock_class); + return @$generator->generateSubclass($methods); + } + + /** + * Generates a version of a class with selected + * methods mocked only. Inherits the old class + * and chains the mock methods of an aggregated + * mock object. + * @param string $class Class to clone. + * @param string $mock_class New class name. + * @param array $methods Methods to be overridden + * with mock versions. + * @access public + */ + static function generatePartial($class, $mock_class, $methods) { + $generator = new MockGenerator($class, $mock_class); + return @$generator->generatePartial($methods); + } + + /** + * Uses a stack trace to find the line of an assertion. + * @access public + */ + static function getExpectationLine() { + $trace = new SimpleStackTrace(array('expect')); + return $trace->traceMethod(); + } +} + +/** + * Service class for code generation of mock objects. + * @package SimpleTest + * @subpackage MockObjects + */ +class MockGenerator { + private $class; + private $mock_class; + private $mock_base; + private $reflection; + + /** + * Builds initial reflection object. + * @param string $class Class to be mocked. + * @param string $mock_class New class with identical interface, + * but no behaviour. + */ + function __construct($class, $mock_class) { + $this->class = $class; + $this->mock_class = $mock_class; + if (! $this->mock_class) { + $this->mock_class = 'Mock' . $this->class; + } + $this->mock_base = SimpleTest::getMockBaseClass(); + $this->reflection = new SimpleReflection($this->class); + } + + /** + * Clones a class' interface and creates a mock version + * that can have return values and expectations set. + * @param array $methods Additional methods to add beyond + * those in th cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet. + * @access public + */ + function generate($methods) { + if (! $this->reflection->classOrInterfaceExists()) { + return false; + } + $mock_reflection = new SimpleReflection($this->mock_class); + if ($mock_reflection->classExistsSansAutoload()) { + return false; + } + $code = $this->createClassCode($methods ? $methods : array()); + return eval("$code return \$code;"); + } + + /** + * Subclasses a class and overrides every method with a mock one + * that can have return values and expectations set. Chains + * to an aggregated SimpleMock. + * @param array $methods Additional methods to add beyond + * those in the cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet. + * @access public + */ + function generateSubclass($methods) { + if (! $this->reflection->classOrInterfaceExists()) { + return false; + } + $mock_reflection = new SimpleReflection($this->mock_class); + if ($mock_reflection->classExistsSansAutoload()) { + return false; + } + if ($this->reflection->isInterface() || $this->reflection->hasFinal()) { + $code = $this->createClassCode($methods ? $methods : array()); + return eval("$code return \$code;"); + } else { + $code = $this->createSubclassCode($methods ? $methods : array()); + return eval("$code return \$code;"); + } + } + + /** + * Generates a version of a class with selected + * methods mocked only. Inherits the old class + * and chains the mock methods of an aggregated + * mock object. + * @param array $methods Methods to be overridden + * with mock versions. + * @access public + */ + function generatePartial($methods) { + if (! $this->reflection->classExists($this->class)) { + return false; + } + $mock_reflection = new SimpleReflection($this->mock_class); + if ($mock_reflection->classExistsSansAutoload()) { + trigger_error('Partial mock class [' . $this->mock_class . '] already exists'); + return false; + } + $code = $this->extendClassCode($methods); + return eval("$code return \$code;"); + } + + /** + * The new mock class code as a string. + * @param array $methods Additional methods. + * @return string Code for new mock class. + * @access private + */ + protected function createClassCode($methods) { + $implements = ''; + $interfaces = $this->reflection->getInterfaces(); + if (function_exists('spl_classes')) { + $interfaces = array_diff($interfaces, array('Traversable')); + } + if (count($interfaces) > 0) { + $implements = 'implements ' . implode(', ', $interfaces); + } + $code = "class " . $this->mock_class . " extends " . $this->mock_base . " $implements {\n"; + $code .= " function " . $this->mock_class . "() {\n"; + $code .= " \$this->" . $this->mock_base . "();\n"; + $code .= " }\n"; + if (in_array('__construct', $this->reflection->getMethods())) { + $code .= " function __construct() {\n"; + $code .= " \$this->" . $this->mock_base . "();\n"; + $code .= " }\n"; + } + $code .= $this->createHandlerCode($methods); + $code .= "}\n"; + return $code; + } + + /** + * The new mock class code as a string. The mock will + * be a subclass of the original mocked class. + * @param array $methods Additional methods. + * @return string Code for new mock class. + * @access private + */ + protected function createSubclassCode($methods) { + $code = "class " . $this->mock_class . " extends " . $this->class . " {\n"; + $code .= " public \$mock;\n"; + $code .= $this->addMethodList(array_merge($methods, $this->reflection->getMethods())); + $code .= "\n"; + $code .= " function " . $this->mock_class . "() {\n"; + $code .= " \$this->mock = new " . $this->mock_base . "();\n"; + $code .= " \$this->mock->disableExpectationNameChecks();\n"; + $code .= " }\n"; + $code .= $this->chainMockReturns(); + $code .= $this->chainMockExpectations(); + $code .= $this->chainThrowMethods(); + $code .= $this->overrideMethods($this->reflection->getMethods()); + $code .= $this->createNewMethodCode($methods); + $code .= "}\n"; + return $code; + } + + /** + * The extension class code as a string. The class + * composites a mock object and chains mocked methods + * to it. + * @param array $methods Mocked methods. + * @return string Code for a new class. + * @access private + */ + protected function extendClassCode($methods) { + $code = "class " . $this->mock_class . " extends " . $this->class . " {\n"; + $code .= " protected \$mock;\n"; + $code .= $this->addMethodList($methods); + $code .= "\n"; + $code .= " function " . $this->mock_class . "() {\n"; + $code .= " \$this->mock = new " . $this->mock_base . "();\n"; + $code .= " \$this->mock->disableExpectationNameChecks();\n"; + $code .= " }\n"; + $code .= $this->chainMockReturns(); + $code .= $this->chainMockExpectations(); + $code .= $this->chainThrowMethods(); + $code .= $this->overrideMethods($methods); + $code .= "}\n"; + return $code; + } + + /** + * Creates code within a class to generate replaced + * methods. All methods call the invoke() handler + * with the method name and the arguments in an + * array. + * @param array $methods Additional methods. + * @access private + */ + protected function createHandlerCode($methods) { + $code = ''; + $methods = array_merge($methods, $this->reflection->getMethods()); + foreach ($methods as $method) { + if ($this->isConstructor($method)) { + continue; + } + $mock_reflection = new SimpleReflection($this->mock_base); + if (in_array($method, $mock_reflection->getMethods())) { + continue; + } + $code .= " " . $this->reflection->getSignature($method) . " {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " \$result = &\$this->invoke(\"$method\", \$args);\n"; + $code .= " return \$result;\n"; + $code .= " }\n"; + } + return $code; + } + + /** + * Creates code within a class to generate a new + * methods. All methods call the invoke() handler + * on the internal mock with the method name and + * the arguments in an array. + * @param array $methods Additional methods. + * @access private + */ + protected function createNewMethodCode($methods) { + $code = ''; + foreach ($methods as $method) { + if ($this->isConstructor($method)) { + continue; + } + $mock_reflection = new SimpleReflection($this->mock_base); + if (in_array($method, $mock_reflection->getMethods())) { + continue; + } + $code .= " " . $this->reflection->getSignature($method) . " {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " \$result = &\$this->mock->invoke(\"$method\", \$args);\n"; + $code .= " return \$result;\n"; + $code .= " }\n"; + } + return $code; + } + + /** + * Tests to see if a special PHP method is about to + * be stubbed by mistake. + * @param string $method Method name. + * @return boolean True if special. + * @access private + */ + protected function isConstructor($method) { + return in_array( + strtolower($method), + array('__construct', '__destruct')); + } + + /** + * Creates a list of mocked methods for error checking. + * @param array $methods Mocked methods. + * @return string Code for a method list. + * @access private + */ + protected function addMethodList($methods) { + return " protected \$mocked_methods = array('" . + implode("', '", array_map('strtolower', $methods)) . + "');\n"; + } + + /** + * Creates code to abandon the expectation if not mocked. + * @param string $alias Parameter name of method name. + * @return string Code for bail out. + * @access private + */ + protected function bailOutIfNotMocked($alias) { + $code = " if (! in_array(strtolower($alias), \$this->mocked_methods)) {\n"; + $code .= " trigger_error(\"Method [$alias] is not mocked\");\n"; + $code .= " \$null = null;\n"; + $code .= " return \$null;\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code for chaining to the composited + * mock object. + * @return string Code for mock set up. + * @access private + */ + protected function chainMockReturns() { + $code = " function returns(\$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->returns(\$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function returnsAt(\$timing, \$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->returnsAt(\$timing, \$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnValue(\$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnValue(\$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnReference(\$method, &\$ref, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnReference(\$method, \$ref, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code for chaining to an aggregated + * mock object. + * @return string Code for expectations. + * @access private + */ + protected function chainMockExpectations() { + $code = " function expect(\$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expect(\$method, \$args, \$msg);\n"; + $code .= " }\n"; + $code .= " function expectAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectAt(\$timing, \$method, \$args, \$msg);\n"; + $code .= " }\n"; + $code .= " function expectCallCount(\$method, \$count) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectCallCount(\$method, \$count, \$msg = '%s');\n"; + $code .= " }\n"; + $code .= " function expectMaximumCallCount(\$method, \$count, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectMaximumCallCount(\$method, \$count, \$msg = '%s');\n"; + $code .= " }\n"; + $code .= " function expectMinimumCallCount(\$method, \$count, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectMinimumCallCount(\$method, \$count, \$msg = '%s');\n"; + $code .= " }\n"; + $code .= " function expectNever(\$method) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectNever(\$method);\n"; + $code .= " }\n"; + $code .= " function expectOnce(\$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectOnce(\$method, \$args, \$msg);\n"; + $code .= " }\n"; + $code .= " function expectAtLeastOnce(\$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectAtLeastOnce(\$method, \$args, \$msg);\n"; + $code .= " }\n"; + return $code; + } + + /** + * Adds code for chaining the throw methods. + * @return string Code for chains. + * @access private + */ + protected function chainThrowMethods() { + $code = " function throwOn(\$method, \$exception = false, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->throwOn(\$method, \$exception, \$args);\n"; + $code .= " }\n"; + $code .= " function throwAt(\$timing, \$method, \$exception = false, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->throwAt(\$timing, \$method, \$exception, \$args);\n"; + $code .= " }\n"; + $code .= " function errorOn(\$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->errorOn(\$method, \$error, \$args, \$severity);\n"; + $code .= " }\n"; + $code .= " function errorAt(\$timing, \$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->errorAt(\$timing, \$method, \$error, \$args, \$severity);\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code to override a list of methods + * with mock versions. + * @param array $methods Methods to be overridden + * with mock versions. + * @return string Code for overridden chains. + * @access private + */ + protected function overrideMethods($methods) { + $code = ""; + foreach ($methods as $method) { + if ($this->isConstructor($method)) { + continue; + } + $code .= " " . $this->reflection->getSignature($method) . " {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " \$result = &\$this->mock->invoke(\"$method\", \$args);\n"; + $code .= " return \$result;\n"; + $code .= " }\n"; + } + return $code; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/page.php b/lib/Swift/test-suite/lib/simpletest/page.php new file mode 100644 index 0000000..cfaa255 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/page.php @@ -0,0 +1,979 @@ + 'SimpleAnchorTag', + 'title' => 'SimpleTitleTag', + 'base' => 'SimpleBaseTag', + 'button' => 'SimpleButtonTag', + 'textarea' => 'SimpleTextAreaTag', + 'option' => 'SimpleOptionTag', + 'label' => 'SimpleLabelTag', + 'form' => 'SimpleFormTag', + 'frame' => 'SimpleFrameTag'); + $attributes = $this->keysToLowerCase($attributes); + if (array_key_exists($name, $map)) { + $tag_class = $map[$name]; + return new $tag_class($attributes); + } elseif ($name == 'select') { + return $this->createSelectionTag($attributes); + } elseif ($name == 'input') { + return $this->createInputTag($attributes); + } + return new SimpleTag($name, $attributes); + } + + /** + * Factory for selection fields. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + protected function createSelectionTag($attributes) { + if (isset($attributes['multiple'])) { + return new MultipleSelectionTag($attributes); + } + return new SimpleSelectionTag($attributes); + } + + /** + * Factory for input tags. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + protected function createInputTag($attributes) { + if (! isset($attributes['type'])) { + return new SimpleTextTag($attributes); + } + $type = strtolower(trim($attributes['type'])); + $map = array( + 'submit' => 'SimpleSubmitTag', + 'image' => 'SimpleImageSubmitTag', + 'checkbox' => 'SimpleCheckboxTag', + 'radio' => 'SimpleRadioButtonTag', + 'text' => 'SimpleTextTag', + 'hidden' => 'SimpleTextTag', + 'password' => 'SimpleTextTag', + 'file' => 'SimpleUploadTag'); + if (array_key_exists($type, $map)) { + $tag_class = $map[$type]; + return new $tag_class($attributes); + } + return false; + } + + /** + * Make the keys lower case for case insensitive look-ups. + * @param hash $map Hash to convert. + * @return hash Unchanged values, but keys lower case. + * @access private + */ + protected function keysToLowerCase($map) { + $lower = array(); + foreach ($map as $key => $value) { + $lower[strtolower($key)] = $value; + } + return $lower; + } +} + +/** + * SAX event handler. Maintains a list of + * open tags and dispatches them as they close. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePageBuilder extends SimpleSaxListener { + private $tags; + private $page; + private $private_content_tag; + + /** + * Sets the builder up empty. + * @access public + */ + function __construct() { + parent::__construct(); + } + + /** + * Frees up any references so as to allow the PHP garbage + * collection from unset() to work. + * @access public + */ + function free() { + unset($this->tags); + unset($this->page); + unset($this->private_content_tags); + } + + /** + * Reads the raw content and send events + * into the page to be built. + * @param $response SimpleHttpResponse Fetched response. + * @return SimplePage Newly parsed page. + * @access public + */ + function parse($response) { + $this->tags = array(); + $this->page = $this->createPage($response); + $parser = $this->createParser($this); + $parser->parse($response->getContent()); + $this->page->acceptPageEnd(); + return $this->page; + } + + /** + * Creates an empty page. + * @return SimplePage New unparsed page. + * @access protected + */ + protected function createPage($response) { + return new SimplePage($response); + } + + /** + * Creates the parser used with the builder. + * @param $listener SimpleSaxListener Target of parser. + * @return SimpleSaxParser Parser to generate + * events for the builder. + * @access protected + */ + protected function createParser(&$listener) { + return new SimpleHtmlSaxParser($listener); + } + + /** + * Start of element event. Opens a new tag. + * @param string $name Element name. + * @param hash $attributes Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + $factory = new SimpleTagBuilder(); + $tag = $factory->createTag($name, $attributes); + if (! $tag) { + return true; + } + if ($tag->getTagName() == 'label') { + $this->page->acceptLabelStart($tag); + $this->openTag($tag); + return true; + } + if ($tag->getTagName() == 'form') { + $this->page->acceptFormStart($tag); + return true; + } + if ($tag->getTagName() == 'frameset') { + $this->page->acceptFramesetStart($tag); + return true; + } + if ($tag->getTagName() == 'frame') { + $this->page->acceptFrame($tag); + return true; + } + if ($tag->isPrivateContent() && ! isset($this->private_content_tag)) { + $this->private_content_tag = &$tag; + } + if ($tag->expectEndTag()) { + $this->openTag($tag); + return true; + } + $this->page->acceptTag($tag); + return true; + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + if ($name == 'label') { + $this->page->acceptLabelEnd(); + return true; + } + if ($name == 'form') { + $this->page->acceptFormEnd(); + return true; + } + if ($name == 'frameset') { + $this->page->acceptFramesetEnd(); + return true; + } + if ($this->hasNamedTagOnOpenTagStack($name)) { + $tag = array_pop($this->tags[$name]); + if ($tag->isPrivateContent() && $this->private_content_tag->getTagName() == $name) { + unset($this->private_content_tag); + } + $this->addContentTagToOpenTags($tag); + $this->page->acceptTag($tag); + return true; + } + return true; + } + + /** + * Test to see if there are any open tags awaiting + * closure that match the tag name. + * @param string $name Element name. + * @return boolean True if any are still open. + * @access private + */ + protected function hasNamedTagOnOpenTagStack($name) { + return isset($this->tags[$name]) && (count($this->tags[$name]) > 0); + } + + /** + * Unparsed, but relevant data. The data is added + * to every open tag. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + if (isset($this->private_content_tag)) { + $this->private_content_tag->addContent($text); + } else { + $this->addContentToAllOpenTags($text); + } + return true; + } + + /** + * Any content fills all currently open tags unless it + * is part of an option tag. + * @param string $text May include unparsed tags. + * @access private + */ + protected function addContentToAllOpenTags($text) { + foreach (array_keys($this->tags) as $name) { + for ($i = 0, $count = count($this->tags[$name]); $i < $count; $i++) { + $this->tags[$name][$i]->addContent($text); + } + } + } + + /** + * Parsed data in tag form. The parsed tag is added + * to every open tag. Used for adding options to select + * fields only. + * @param SimpleTag $tag Option tags only. + * @access private + */ + protected function addContentTagToOpenTags(&$tag) { + if ($tag->getTagName() != 'option') { + return; + } + foreach (array_keys($this->tags) as $name) { + for ($i = 0, $count = count($this->tags[$name]); $i < $count; $i++) { + $this->tags[$name][$i]->addTag($tag); + } + } + } + + /** + * Opens a tag for receiving content. Multiple tags + * will be receiving input at the same time. + * @param SimpleTag $tag New content tag. + * @access private + */ + protected function openTag($tag) { + $name = $tag->getTagName(); + if (! in_array($name, array_keys($this->tags))) { + $this->tags[$name] = array(); + } + $this->tags[$name][] = $tag; + } +} + +/** + * A wrapper for a web page. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePage { + private $links; + private $title; + private $last_widget; + private $label; + private $left_over_labels; + private $open_forms; + private $complete_forms; + private $frameset; + private $frames; + private $frameset_nesting_level; + private $transport_error; + private $raw; + private $text; + private $sent; + private $headers; + private $method; + private $url; + private $base = false; + private $request_data; + + /** + * Parses a page ready to access it's contents. + * @param SimpleHttpResponse $response Result of HTTP fetch. + * @access public + */ + function __construct($response = false) { + $this->links = array(); + $this->title = false; + $this->left_over_labels = array(); + $this->open_forms = array(); + $this->complete_forms = array(); + $this->frameset = false; + $this->frames = array(); + $this->frameset_nesting_level = 0; + $this->text = false; + if ($response) { + $this->extractResponse($response); + } else { + $this->noResponse(); + } + } + + /** + * Extracts all of the response information. + * @param SimpleHttpResponse $response Response being parsed. + * @access private + */ + protected function extractResponse($response) { + $this->transport_error = $response->getError(); + $this->raw = $response->getContent(); + $this->sent = $response->getSent(); + $this->headers = $response->getHeaders(); + $this->method = $response->getMethod(); + $this->url = $response->getUrl(); + $this->request_data = $response->getRequestData(); + } + + /** + * Sets up a missing response. + * @access private + */ + protected function noResponse() { + $this->transport_error = 'No page fetched yet'; + $this->raw = false; + $this->sent = false; + $this->headers = false; + $this->method = 'GET'; + $this->url = false; + $this->request_data = false; + } + + /** + * Original request as bytes sent down the wire. + * @return mixed Sent content. + * @access public + */ + function getRequest() { + return $this->sent; + } + + /** + * Accessor for raw text of page. + * @return string Raw unparsed content. + * @access public + */ + function getRaw() { + return $this->raw; + } + + /** + * Accessor for plain text of page as a text browser + * would see it. + * @return string Plain text of page. + * @access public + */ + function getText() { + if (! $this->text) { + $this->text = SimpleHtmlSaxParser::normalise($this->raw); + } + return $this->text; + } + + /** + * Accessor for raw headers of page. + * @return string Header block as text. + * @access public + */ + function getHeaders() { + if ($this->headers) { + return $this->headers->getRaw(); + } + return false; + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->method; + } + + /** + * Original resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->url; + } + + /** + * Base URL if set via BASE tag page url otherwise + * @return SimpleUrl Base url. + * @access public + */ + function getBaseUrl() { + return $this->base; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->request_data; + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + return $this->transport_error; + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + if ($this->headers) { + return $this->headers->getMimeType(); + } + return false; + } + + /** + * Accessor for HTTP response code. + * @return integer HTTP response code received. + * @access public + */ + function getResponseCode() { + if ($this->headers) { + return $this->headers->getResponseCode(); + } + return false; + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + if ($this->headers) { + return $this->headers->getAuthentication(); + } + return false; + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + if ($this->headers) { + return $this->headers->getRealm(); + } + return false; + } + + /** + * Accessor for current frame focus. Will be + * false as no frames. + * @return array Always empty. + * @access public + */ + function getFrameFocus() { + return array(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean Always false. + * @access public + */ + function setFrameFocusByIndex($choice) { + return false; + } + + /** + * Sets the focus by name. Always fails for a leaf page. + * @param string $name Chosen frame. + * @return boolean False as no frames. + * @access public + */ + function setFrameFocus($name) { + return false; + } + + /** + * Clears the frame focus. Does nothing for a leaf page. + * @access public + */ + function clearFrameFocus() { + } + + /** + * Adds a tag to the page. + * @param SimpleTag $tag Tag to accept. + * @access public + */ + function acceptTag($tag) { + if ($tag->getTagName() == "a") { + $this->addLink($tag); + } elseif ($tag->getTagName() == "base") { + $this->setBase($tag); + } elseif ($tag->getTagName() == "title") { + $this->setTitle($tag); + } elseif ($this->isFormElement($tag->getTagName())) { + for ($i = 0; $i < count($this->open_forms); $i++) { + $this->open_forms[$i]->addWidget($tag); + } + $this->last_widget = &$tag; + } + } + + /** + * Opens a label for a described widget. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + function acceptLabelStart($tag) { + $this->label = $tag; + unset($this->last_widget); + } + + /** + * Closes the most recently opened label. + * @access public + */ + function acceptLabelEnd() { + if (isset($this->label)) { + if (isset($this->last_widget)) { + $this->last_widget->setLabel($this->label->getText()); + unset($this->last_widget); + } else { + $this->left_over_labels[] = SimpleTestCompatibility::copy($this->label); + } + unset($this->label); + } + } + + /** + * Tests to see if a tag is a possible form + * element. + * @param string $name HTML element name. + * @return boolean True if form element. + * @access private + */ + protected function isFormElement($name) { + return in_array($name, array('input', 'button', 'textarea', 'select')); + } + + /** + * Opens a form. New widgets go here. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + function acceptFormStart($tag) { + $this->open_forms[] = new SimpleForm($tag, $this); + } + + /** + * Closes the most recently opened form. + * @access public + */ + function acceptFormEnd() { + if (count($this->open_forms)) { + $this->complete_forms[] = array_pop($this->open_forms); + } + } + + /** + * Opens a frameset. A frameset may contain nested + * frameset tags. + * @param SimpleFramesetTag $tag Tag to accept. + * @access public + */ + function acceptFramesetStart($tag) { + if (! $this->isLoadingFrames()) { + $this->frameset = $tag; + } + $this->frameset_nesting_level++; + } + + /** + * Closes the most recently opened frameset. + * @access public + */ + function acceptFramesetEnd() { + if ($this->isLoadingFrames()) { + $this->frameset_nesting_level--; + } + } + + /** + * Takes a single frame tag and stashes it in + * the current frame set. + * @param SimpleFrameTag $tag Tag to accept. + * @access public + */ + function acceptFrame($tag) { + if ($this->isLoadingFrames()) { + if ($tag->getAttribute('src')) { + $this->frames[] = $tag; + } + } + } + + /** + * Test to see if in the middle of reading + * a frameset. + * @return boolean True if inframeset. + * @access private + */ + protected function isLoadingFrames() { + if (! $this->frameset) { + return false; + } + return ($this->frameset_nesting_level > 0); + } + + /** + * Test to see if link is an absolute one. + * @param string $url Url to test. + * @return boolean True if absolute. + * @access protected + */ + protected function linkIsAbsolute($url) { + $parsed = new SimpleUrl($url); + return (boolean)($parsed->getScheme() && $parsed->getHost()); + } + + /** + * Adds a link to the page. + * @param SimpleAnchorTag $tag Link to accept. + * @access protected + */ + protected function addLink($tag) { + $this->links[] = $tag; + } + + /** + * Marker for end of complete page. Any work in + * progress can now be closed. + * @access public + */ + function acceptPageEnd() { + while (count($this->open_forms)) { + $this->complete_forms[] = array_pop($this->open_forms); + } + foreach ($this->left_over_labels as $label) { + for ($i = 0, $count = count($this->complete_forms); $i < $count; $i++) { + $this->complete_forms[$i]->attachLabelBySelector( + new SimpleById($label->getFor()), + $label->getText()); + } + } + } + + /** + * Test for the presence of a frameset. + * @return boolean True if frameset. + * @access public + */ + function hasFrames() { + return (boolean)$this->frameset; + } + + /** + * Accessor for frame name and source URL for every frame that + * will need to be loaded. Immediate children only. + * @return boolean/array False if no frameset or + * otherwise a hash of frame URLs. + * The key is either a numerical + * base one index or the name attribute. + * @access public + */ + function getFrameset() { + if (! $this->frameset) { + return false; + } + $urls = array(); + for ($i = 0; $i < count($this->frames); $i++) { + $name = $this->frames[$i]->getAttribute('name'); + $url = new SimpleUrl($this->frames[$i]->getAttribute('src')); + $urls[$name ? $name : $i + 1] = $this->expandUrl($url); + } + return $urls; + } + + /** + * Fetches a list of loaded frames. + * @return array/string Just the URL for a single page. + * @access public + */ + function getFrames() { + $url = $this->expandUrl($this->getUrl()); + return $url->asString(); + } + + /** + * Accessor for a list of all links. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getUrls() { + $all = array(); + foreach ($this->links as $link) { + $url = $this->getUrlFromLink($link); + $all[] = $url->asString(); + } + return $all; + } + + /** + * Accessor for URLs by the link label. Label will match + * regardess of whitespace issues and case. + * @param string $label Text of link. + * @return array List of links with that label. + * @access public + */ + function getUrlsByLabel($label) { + $matches = array(); + foreach ($this->links as $link) { + if ($link->getText() == $label) { + $matches[] = $this->getUrlFromLink($link); + } + } + return $matches; + } + + /** + * Accessor for a URL by the id attribute. + * @param string $id Id attribute of link. + * @return SimpleUrl URL with that id of false if none. + * @access public + */ + function getUrlById($id) { + foreach ($this->links as $link) { + if ($link->getAttribute('id') === (string)$id) { + return $this->getUrlFromLink($link); + } + } + return false; + } + + /** + * Converts a link tag into a target URL. + * @param SimpleAnchor $link Parsed link. + * @return SimpleUrl URL with frame target if any. + * @access private + */ + protected function getUrlFromLink($link) { + $url = $this->expandUrl($link->getHref()); + if ($link->getAttribute('target')) { + $url->setTarget($link->getAttribute('target')); + } + return $url; + } + + /** + * Expands expandomatic URLs into fully qualified + * URLs. + * @param SimpleUrl $url Relative URL. + * @return SimpleUrl Absolute URL. + * @access public + */ + function expandUrl($url) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + $location = $this->getBaseUrl() ? $this->getBaseUrl() : new SimpleUrl(); + return $url->makeAbsolute($location->makeAbsolute($this->getUrl())); + } + + /** + * Sets the base url for the page. + * @param SimpleTag $tag Base URL for page. + * @access protected + */ + protected function setBase($tag) { + $url = $tag->getAttribute('href'); + $this->base = new SimpleUrl($url); + } + + /** + * Sets the title tag contents. + * @param SimpleTitleTag $tag Title of page. + * @access protected + */ + protected function setTitle($tag) { + $this->title = $tag; + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + if ($this->title) { + return $this->title->getText(); + } + return false; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. + * @param SimpleSelector $selector Button finder. + * @return SimpleForm Form object containing + * the button. + * @access public + */ + function &getFormBySubmit($selector) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->hasSubmit($selector)) { + return $this->complete_forms[$i]; + } + } + $null = null; + return $null; + } + + /** + * Finds a held form by image using a selector. + * Will only search correctly built forms. + * @param SimpleSelector $selector Image finder. + * @return SimpleForm Form object containing + * the image. + * @access public + */ + function getFormByImage($selector) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->hasImage($selector)) { + return $this->complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by the form ID. A way of + * identifying a specific form when we have control + * of the HTML code. + * @param string $id Form label. + * @return SimpleForm Form object containing the matching ID. + * @access public + */ + function getFormById($id) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->getId() == $id) { + return $this->complete_forms[$i]; + } + } + return null; + } + + /** + * Sets a field on each form in which the field is + * available. + * @param SimpleSelector $selector Field finder. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setField($selector, $value, $position=false) { + $is_set = false; + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->setField($selector, $value, $position)) { + $is_set = true; + } + } + return $is_set; + } + + /** + * Accessor for a form element value within a page. + * @param SimpleSelector $selector Field finder. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($selector) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + $value = $this->complete_forms[$i]->getValue($selector); + if (isset($value)) { + return $value; + } + } + return null; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/parser.php b/lib/Swift/test-suite/lib/simpletest/parser.php new file mode 100644 index 0000000..e95df81 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/parser.php @@ -0,0 +1,760 @@ + $constant) { + if (! defined($constant)) { + define($constant, $i + 1); + } +} +/**#@-*/ + +/** + * Compounded regular expression. Any of + * the contained patterns could match and + * when one does, it's label is returned. + * @package SimpleTest + * @subpackage WebTester + */ +class ParallelRegex { + private $patterns; + private $labels; + private $regex; + private $case; + + /** + * Constructor. Starts with no patterns. + * @param boolean $case True for case sensitive, false + * for insensitive. + * @access public + */ + function __construct($case) { + $this->case = $case; + $this->patterns = array(); + $this->labels = array(); + $this->regex = null; + } + + /** + * Adds a pattern with an optional label. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $label Label of regex to be returned + * on a match. + * @access public + */ + function addPattern($pattern, $label = true) { + $count = count($this->patterns); + $this->patterns[$count] = $pattern; + $this->labels[$count] = $label; + $this->regex = null; + } + + /** + * Attempts to match all patterns at once against + * a string. + * @param string $subject String to match against. + * @param string $match First matched portion of + * subject. + * @return boolean True on success. + * @access public + */ + function match($subject, &$match) { + if (count($this->patterns) == 0) { + return false; + } + if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { + $match = ''; + return false; + } + $match = $matches[0]; + for ($i = 1; $i < count($matches); $i++) { + if ($matches[$i]) { + return $this->labels[$i - 1]; + } + } + return true; + } + + /** + * Compounds the patterns into a single + * regular expression separated with the + * "or" operator. Caches the regex. + * Will automatically escape (, ) and / tokens. + * @param array $patterns List of patterns in order. + * @access private + */ + protected function getCompoundedRegex() { + if ($this->regex == null) { + for ($i = 0, $count = count($this->patterns); $i < $count; $i++) { + $this->patterns[$i] = '(' . str_replace( + array('/', '(', ')'), + array('\/', '\(', '\)'), + $this->patterns[$i]) . ')'; + } + $this->regex = "/" . implode("|", $this->patterns) . "/" . $this->getPerlMatchingFlags(); + } + return $this->regex; + } + + /** + * Accessor for perl regex mode flags to use. + * @return string Perl regex flags. + * @access private + */ + protected function getPerlMatchingFlags() { + return ($this->case ? "msS" : "msSi"); + } +} + +/** + * States for a stack machine. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleStateStack { + private $stack; + + /** + * Constructor. Starts in named state. + * @param string $start Starting state name. + * @access public + */ + function __construct($start) { + $this->stack = array($start); + } + + /** + * Accessor for current state. + * @return string State. + * @access public + */ + function getCurrent() { + return $this->stack[count($this->stack) - 1]; + } + + /** + * Adds a state to the stack and sets it + * to be the current state. + * @param string $state New state. + * @access public + */ + function enter($state) { + array_push($this->stack, $state); + } + + /** + * Leaves the current state and reverts + * to the previous one. + * @return boolean False if we drop off + * the bottom of the list. + * @access public + */ + function leave() { + if (count($this->stack) == 1) { + return false; + } + array_pop($this->stack); + return true; + } +} + +/** + * Accepts text and breaks it into tokens. + * Some optimisation to make the sure the + * content is only scanned by the PHP regex + * parser once. Lexer modes must not start + * with leading underscores. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleLexer { + private $regexes; + private $parser; + private $mode; + private $mode_handlers; + private $case; + + /** + * Sets up the lexer in case insensitive matching + * by default. + * @param SimpleSaxParser $parser Handling strategy by + * reference. + * @param string $start Starting handler. + * @param boolean $case True for case sensitive. + * @access public + */ + function __construct($parser, $start = "accept", $case = false) { + $this->case = $case; + $this->regexes = array(); + $this->parser = $parser; + $this->mode = new SimpleStateStack($start); + $this->mode_handlers = array($start => $start); + } + + /** + * Adds a token search pattern for a particular + * parsing mode. The pattern does not change the + * current mode. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @access public + */ + function addPattern($pattern, $mode = "accept") { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern); + if (! isset($this->mode_handlers[$mode])) { + $this->mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that will enter a new parsing + * mode. Useful for entering parenthesis, strings, + * tags, etc. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $new_mode Change parsing to this new + * nested mode. + * @access public + */ + function addEntryPattern($pattern, $mode, $new_mode) { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, $new_mode); + if (! isset($this->mode_handlers[$new_mode])) { + $this->mode_handlers[$new_mode] = $new_mode; + } + } + + /** + * Adds a pattern that will exit the current mode + * and re-enter the previous one. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Mode to leave. + * @access public + */ + function addExitPattern($pattern, $mode) { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "__exit"); + if (! isset($this->mode_handlers[$mode])) { + $this->mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that has a special mode. Acts as an entry + * and exit pattern in one go, effectively calling a special + * parser handler for this token only. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $special Use this mode for this one token. + * @access public + */ + function addSpecialPattern($pattern, $mode, $special) { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "_$special"); + if (! isset($this->mode_handlers[$special])) { + $this->mode_handlers[$special] = $special; + } + } + + /** + * Adds a mapping from a mode to another handler. + * @param string $mode Mode to be remapped. + * @param string $handler New target handler. + * @access public + */ + function mapHandler($mode, $handler) { + $this->mode_handlers[$mode] = $handler; + } + + /** + * Splits the page text into tokens. Will fail + * if the handlers report an error or if no + * content is consumed. If successful then each + * unparsed and parsed token invokes a call to the + * held listener. + * @param string $raw Raw HTML text. + * @return boolean True on success, else false. + * @access public + */ + function parse($raw) { + if (! isset($this->parser)) { + return false; + } + $length = strlen($raw); + while (is_array($parsed = $this->reduce($raw))) { + list($raw, $unmatched, $matched, $mode) = $parsed; + if (! $this->dispatchTokens($unmatched, $matched, $mode)) { + return false; + } + if ($raw === '') { + return true; + } + if (strlen($raw) == $length) { + return false; + } + $length = strlen($raw); + } + if (! $parsed) { + return false; + } + return $this->invokeParser($raw, LEXER_UNMATCHED); + } + + /** + * Sends the matched token and any leading unmatched + * text to the parser changing the lexer to a new + * mode if one is listed. + * @param string $unmatched Unmatched leading portion. + * @param string $matched Actual token match. + * @param string $mode Mode after match. A boolean + * false mode causes no change. + * @return boolean False if there was any error + * from the parser. + * @access private + */ + protected function dispatchTokens($unmatched, $matched, $mode = false) { + if (! $this->invokeParser($unmatched, LEXER_UNMATCHED)) { + return false; + } + if (is_bool($mode)) { + return $this->invokeParser($matched, LEXER_MATCHED); + } + if ($this->isModeEnd($mode)) { + if (! $this->invokeParser($matched, LEXER_EXIT)) { + return false; + } + return $this->mode->leave(); + } + if ($this->isSpecialMode($mode)) { + $this->mode->enter($this->decodeSpecial($mode)); + if (! $this->invokeParser($matched, LEXER_SPECIAL)) { + return false; + } + return $this->mode->leave(); + } + $this->mode->enter($mode); + return $this->invokeParser($matched, LEXER_ENTER); + } + + /** + * Tests to see if the new mode is actually to leave + * the current mode and pop an item from the matching + * mode stack. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + protected function isModeEnd($mode) { + return ($mode === "__exit"); + } + + /** + * Test to see if the mode is one where this mode + * is entered for this token only and automatically + * leaves immediately afterwoods. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + protected function isSpecialMode($mode) { + return (strncmp($mode, "_", 1) == 0); + } + + /** + * Strips the magic underscore marking single token + * modes. + * @param string $mode Mode to decode. + * @return string Underlying mode name. + * @access private + */ + protected function decodeSpecial($mode) { + return substr($mode, 1); + } + + /** + * Calls the parser method named after the current + * mode. Empty content will be ignored. The lexer + * has a parser handler for each mode in the lexer. + * @param string $content Text parsed. + * @param boolean $is_match Token is recognised rather + * than unparsed data. + * @access private + */ + protected function invokeParser($content, $is_match) { + if (($content === '') || ($content === false)) { + return true; + } + $handler = $this->mode_handlers[$this->mode->getCurrent()]; + return $this->parser->$handler($content, $is_match); + } + + /** + * Tries to match a chunk of text and if successful + * removes the recognised chunk and any leading + * unparsed data. Empty strings will not be matched. + * @param string $raw The subject to parse. This is the + * content that will be eaten. + * @return array/boolean Three item list of unparsed + * content followed by the + * recognised token and finally the + * action the parser is to take. + * True if no match, false if there + * is a parsing error. + * @access private + */ + protected function reduce($raw) { + if ($action = $this->regexes[$this->mode->getCurrent()]->match($raw, $match)) { + $unparsed_character_count = strpos($raw, $match); + $unparsed = substr($raw, 0, $unparsed_character_count); + $raw = substr($raw, $unparsed_character_count + strlen($match)); + return array($raw, $unparsed, $match, $action); + } + return true; + } +} + +/** + * Breaks HTML into SAX events. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHtmlLexer extends SimpleLexer { + + /** + * Sets up the lexer with case insensitive matching + * and adds the HTML handlers. + * @param SimpleSaxParser $parser Handling strategy by + * reference. + * @access public + */ + function __construct($parser) { + parent::__construct($parser, 'text'); + $this->mapHandler('text', 'acceptTextToken'); + $this->addSkipping(); + foreach ($this->getParsedTags() as $tag) { + $this->addTag($tag); + } + $this->addInTagTokens(); + } + + /** + * List of parsed tags. Others are ignored. + * @return array List of searched for tags. + * @access private + */ + protected function getParsedTags() { + return array('a', 'base', 'title', 'form', 'input', 'button', 'textarea', 'select', + 'option', 'frameset', 'frame', 'label'); + } + + /** + * The lexer has to skip certain sections such + * as server code, client code and styles. + * @access private + */ + protected function addSkipping() { + $this->mapHandler('css', 'ignore'); + $this->addEntryPattern('addExitPattern('', 'css'); + $this->mapHandler('js', 'ignore'); + $this->addEntryPattern('addExitPattern('', 'js'); + $this->mapHandler('comment', 'ignore'); + $this->addEntryPattern('', 'comment'); + } + + /** + * Pattern matches to start and end a tag. + * @param string $tag Name of tag to scan for. + * @access private + */ + protected function addTag($tag) { + $this->addSpecialPattern("", 'text', 'acceptEndToken'); + $this->addEntryPattern("<$tag", 'text', 'tag'); + } + + /** + * Pattern matches to parse the inside of a tag + * including the attributes and their quoting. + * @access private + */ + protected function addInTagTokens() { + $this->mapHandler('tag', 'acceptStartToken'); + $this->addSpecialPattern('\s+', 'tag', 'ignore'); + $this->addAttributeTokens(); + $this->addExitPattern('/>', 'tag'); + $this->addExitPattern('>', 'tag'); + } + + /** + * Matches attributes that are either single quoted, + * double quoted or unquoted. + * @access private + */ + protected function addAttributeTokens() { + $this->mapHandler('dq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern('=\s*"', 'tag', 'dq_attribute'); + $this->addPattern("\\\\\"", 'dq_attribute'); + $this->addExitPattern('"', 'dq_attribute'); + $this->mapHandler('sq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern("=\s*'", 'tag', 'sq_attribute'); + $this->addPattern("\\\\'", 'sq_attribute'); + $this->addExitPattern("'", 'sq_attribute'); + $this->mapHandler('uq_attribute', 'acceptAttributeToken'); + $this->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute'); + } +} + +/** + * Converts HTML tokens into selected SAX events. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHtmlSaxParser { + private $lexer; + private $listener; + private $tag; + private $attributes; + private $current_attribute; + + /** + * Sets the listener. + * @param SimpleSaxListener $listener SAX event handler. + * @access public + */ + function __construct($listener) { + $this->listener = $listener; + $this->lexer = $this->createLexer($this); + $this->tag = ''; + $this->attributes = array(); + $this->current_attribute = ''; + } + + /** + * Runs the content through the lexer which + * should call back to the acceptors. + * @param string $raw Page text to parse. + * @return boolean False if parse error. + * @access public + */ + function parse($raw) { + return $this->lexer->parse($raw); + } + + /** + * Sets up the matching lexer. Starts in 'text' mode. + * @param SimpleSaxParser $parser Event generator, usually $self. + * @return SimpleLexer Lexer suitable for this parser. + * @access public + */ + static function createLexer(&$parser) { + return new SimpleHtmlLexer($parser); + } + + /** + * Accepts a token from the tag mode. If the + * starting element completes then the element + * is dispatched and the current attributes + * set back to empty. The element or attribute + * name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptStartToken($token, $event) { + if ($event == LEXER_ENTER) { + $this->tag = strtolower(substr($token, 1)); + return true; + } + if ($event == LEXER_EXIT) { + $success = $this->listener->startElement( + $this->tag, + $this->attributes); + $this->tag = ''; + $this->attributes = array(); + return $success; + } + if ($token != '=') { + $this->current_attribute = strtolower(SimpleHtmlSaxParser::decodeHtml($token)); + $this->attributes[$this->current_attribute] = ''; + } + return true; + } + + /** + * Accepts a token from the end tag mode. + * The element name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEndToken($token, $event) { + if (! preg_match('/<\/(.*)>/', $token, $matches)) { + return false; + } + return $this->listener->endElement(strtolower($matches[1])); + } + + /** + * Part of the tag data. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptAttributeToken($token, $event) { + if ($this->current_attribute) { + if ($event == LEXER_UNMATCHED) { + $this->attributes[$this->current_attribute] .= + SimpleHtmlSaxParser::decodeHtml($token); + } + if ($event == LEXER_SPECIAL) { + $this->attributes[$this->current_attribute] .= + preg_replace('/^=\s*/' , '', SimpleHtmlSaxParser::decodeHtml($token)); + } + } + return true; + } + + /** + * A character entity. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEntityToken($token, $event) { + } + + /** + * Character data between tags regarded as + * important. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptTextToken($token, $event) { + return $this->listener->addContent($token); + } + + /** + * Incoming data to be ignored. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function ignore($token, $event) { + return true; + } + + /** + * Decodes any HTML entities. + * @param string $html Incoming HTML. + * @return string Outgoing plain text. + * @access public + */ + static function decodeHtml($html) { + return html_entity_decode($html, ENT_QUOTES); + } + + /** + * Turns HTML into text browser visible text. Images + * are converted to their alt text and tags are supressed. + * Entities are converted to their visible representation. + * @param string $html HTML to convert. + * @return string Plain text. + * @access public + */ + static function normalise($html) { + $text = preg_replace('||', '', $html); + $text = preg_replace('|]*>.*?|', '', $text); + $text = preg_replace('|]*alt\s*=\s*"([^"]*)"[^>]*>|', ' \1 ', $text); + $text = preg_replace('|]*alt\s*=\s*\'([^\']*)\'[^>]*>|', ' \1 ', $text); + $text = preg_replace('|]*alt\s*=\s*([a-zA-Z_]+)[^>]*>|', ' \1 ', $text); + $text = preg_replace('|<[^>]*>|', '', $text); + $text = SimpleHtmlSaxParser::decodeHtml($text); + $text = preg_replace('|\s+|', ' ', $text); + return trim(trim($text), "\xA0"); // TODO: The \xAO is a  . Add a test for this. + } +} + +/** + * SAX event handler. + * @package SimpleTest + * @subpackage WebTester + * @abstract + */ +class SimpleSaxListener { + + /** + * Sets the document to write to. + * @access public + */ + function __construct() { + } + + /** + * Start of element event. + * @param string $name Element name. + * @param hash $attributes Name value pairs. + * Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + } + + /** + * Unparsed, but relevant data. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/reflection_php4.php b/lib/Swift/test-suite/lib/simpletest/reflection_php4.php new file mode 100644 index 0000000..6c93915 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/reflection_php4.php @@ -0,0 +1,136 @@ +_interface = $interface; + } + + /** + * Checks that a class has been declared. + * @return boolean True if defined. + * @access public + */ + function classExists() { + return class_exists($this->_interface); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classExistsSansAutoload() { + return class_exists($this->_interface); + } + + /** + * Checks that a class or interface has been + * declared. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExists() { + return class_exists($this->_interface); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExistsSansAutoload() { + return class_exists($this->_interface); + } + + /** + * Gets the list of methods on a class or + * interface. + * @returns array List of method names. + * @access public + */ + function getMethods() { + return get_class_methods($this->_interface); + } + + /** + * Gets the list of interfaces from a class. If the + * class name is actually an interface then just that + * interface is returned. + * @returns array List of interfaces. + * @access public + */ + function getInterfaces() { + return array(); + } + + /** + * Finds the parent class name. + * @returns string Parent class name. + * @access public + */ + function getParent() { + return strtolower(get_parent_class($this->_interface)); + } + + /** + * Determines if the class is abstract, which for PHP 4 + * will never be the case. + * @returns boolean True if abstract. + * @access public + */ + function isAbstract() { + return false; + } + + /** + * Determines if the the entity is an interface, which for PHP 4 + * will never be the case. + * @returns boolean True if interface. + * @access public + */ + function isInterface() { + return false; + } + + /** + * Scans for final methods, but as it's PHP 4 there + * aren't any. + * @returns boolean True if the class has a final method. + * @access public + */ + function hasFinal() { + return false; + } + + /** + * Gets the source code matching the declaration + * of a method. + * @param string $method Method name. + * @access public + */ + function getSignature($method) { + return "function &$method()"; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/reflection_php5.php b/lib/Swift/test-suite/lib/simpletest/reflection_php5.php new file mode 100644 index 0000000..02cceb5 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/reflection_php5.php @@ -0,0 +1,386 @@ +interface = $interface; + } + + /** + * Checks that a class has been declared. Versions + * before PHP5.0.2 need a check that it's not really + * an interface. + * @return boolean True if defined. + * @access public + */ + function classExists() { + if (! class_exists($this->interface)) { + return false; + } + $reflection = new ReflectionClass($this->interface); + return ! $reflection->isInterface(); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classExistsSansAutoload() { + return class_exists($this->interface, false); + } + + /** + * Checks that a class or interface has been + * declared. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExists() { + return $this->classOrInterfaceExistsWithAutoload($this->interface, true); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExistsSansAutoload() { + return $this->classOrInterfaceExistsWithAutoload($this->interface, false); + } + + /** + * Needed to select the autoload feature in PHP5 + * for classes created dynamically. + * @param string $interface Class or interface name. + * @param boolean $autoload True totriggerautoload. + * @return boolean True if interface defined. + * @access private + */ + protected function classOrInterfaceExistsWithAutoload($interface, $autoload) { + if (function_exists('interface_exists')) { + if (interface_exists($this->interface, $autoload)) { + return true; + } + } + return class_exists($this->interface, $autoload); + } + + /** + * Gets the list of methods on a class or + * interface. + * @returns array List of method names. + * @access public + */ + function getMethods() { + return array_unique(get_class_methods($this->interface)); + } + + /** + * Gets the list of interfaces from a class. If the + * class name is actually an interface then just that + * interface is returned. + * @returns array List of interfaces. + * @access public + */ + function getInterfaces() { + $reflection = new ReflectionClass($this->interface); + if ($reflection->isInterface()) { + return array($this->interface); + } + return $this->onlyParents($reflection->getInterfaces()); + } + + /** + * Gets the list of methods for the implemented + * interfaces only. + * @returns array List of enforced method signatures. + * @access public + */ + function getInterfaceMethods() { + $methods = array(); + foreach ($this->getInterfaces() as $interface) { + $methods = array_merge($methods, get_class_methods($interface)); + } + return array_unique($methods); + } + + /** + * Checks to see if the method signature has to be tightly + * specified. + * @param string $method Method name. + * @returns boolean True if enforced. + * @access private + */ + protected function isInterfaceMethod($method) { + return in_array($method, $this->getInterfaceMethods()); + } + + /** + * Finds the parent class name. + * @returns string Parent class name. + * @access public + */ + function getParent() { + $reflection = new ReflectionClass($this->interface); + $parent = $reflection->getParentClass(); + if ($parent) { + return $parent->getName(); + } + return false; + } + + /** + * Trivially determines if the class is abstract. + * @returns boolean True if abstract. + * @access public + */ + function isAbstract() { + $reflection = new ReflectionClass($this->interface); + return $reflection->isAbstract(); + } + + /** + * Trivially determines if the class is an interface. + * @returns boolean True if interface. + * @access public + */ + function isInterface() { + $reflection = new ReflectionClass($this->interface); + return $reflection->isInterface(); + } + + /** + * Scans for final methods, as they screw up inherited + * mocks by not allowing you to override them. + * @returns boolean True if the class has a final method. + * @access public + */ + function hasFinal() { + $reflection = new ReflectionClass($this->interface); + foreach ($reflection->getMethods() as $method) { + if ($method->isFinal()) { + return true; + } + } + return false; + } + + /** + * Whittles a list of interfaces down to only the + * necessary top level parents. + * @param array $interfaces Reflection API interfaces + * to reduce. + * @returns array List of parent interface names. + * @access private + */ + protected function onlyParents($interfaces) { + $parents = array(); + $blacklist = array(); + foreach ($interfaces as $interface) { + foreach($interfaces as $possible_parent) { + if ($interface->getName() == $possible_parent->getName()) { + continue; + } + if ($interface->isSubClassOf($possible_parent)) { + $blacklist[$possible_parent->getName()] = true; + } + } + if (!isset($blacklist[$interface->getName()])) { + $parents[] = $interface->getName(); + } + } + return $parents; + } + + /** + * Checks whether a method is abstract or not. + * @param string $name Method name. + * @return bool true if method is abstract, else false + * @access private + */ + protected function isAbstractMethod($name) { + $interface = new ReflectionClass($this->interface); + if (! $interface->hasMethod($name)) { + return false; + } + return $interface->getMethod($name)->isAbstract(); + } + + /** + * Checks whether a method is the constructor. + * @param string $name Method name. + * @return bool true if method is the constructor + * @access private + */ + protected function isConstructor($name) { + return ($name == '__construct') || ($name == $this->interface); + } + + /** + * Checks whether a method is abstract in all parents or not. + * @param string $name Method name. + * @return bool true if method is abstract in parent, else false + * @access private + */ + protected function isAbstractMethodInParents($name) { + $interface = new ReflectionClass($this->interface); + $parent = $interface->getParentClass(); + while($parent) { + if (! $parent->hasMethod($name)) { + return false; + } + if ($parent->getMethod($name)->isAbstract()) { + return true; + } + $parent = $parent->getParentClass(); + } + return false; + } + + /** + * Checks whether a method is static or not. + * @param string $name Method name + * @return bool true if method is static, else false + * @access private + */ + protected function isStaticMethod($name) { + $interface = new ReflectionClass($this->interface); + if (! $interface->hasMethod($name)) { + return false; + } + return $interface->getMethod($name)->isStatic(); + } + + /** + * Writes the source code matching the declaration + * of a method. + * @param string $name Method name. + * @return string Method signature up to last + * bracket. + * @access public + */ + function getSignature($name) { + if ($name == '__set') { + return 'function __set($key, $value)'; + } + if ($name == '__call') { + return 'function __call($method, $arguments)'; + } + if (version_compare(phpversion(), '5.1.0', '>=')) { + if (in_array($name, array('__get', '__isset', $name == '__unset'))) { + return "function {$name}(\$key)"; + } + } + if ($name == '__toString') { + return "function $name()"; + } + + // This wonky try-catch is a work around for a faulty method_exists() + // in early versions of PHP 5 which would return false for static + // methods. The Reflection classes work fine, but hasMethod() + // doesn't exist prior to PHP 5.1.0, so we need to use a more crude + // detection method. + try { + $interface = new ReflectionClass($this->interface); + $interface->getMethod($name); + } catch (ReflectionException $e) { + return "function $name()"; + } + return $this->getFullSignature($name); + } + + /** + * For a signature specified in an interface, full + * details must be replicated to be a valid implementation. + * @param string $name Method name. + * @return string Method signature up to last + * bracket. + * @access private + */ + protected function getFullSignature($name) { + $interface = new ReflectionClass($this->interface); + $method = $interface->getMethod($name); + $reference = $method->returnsReference() ? '&' : ''; + $static = $method->isStatic() ? 'static ' : ''; + return "{$static}function $reference$name(" . + implode(', ', $this->getParameterSignatures($method)) . + ")"; + } + + /** + * Gets the source code for each parameter. + * @param ReflectionMethod $method Method object from + * reflection API + * @return array List of strings, each + * a snippet of code. + * @access private + */ + protected function getParameterSignatures($method) { + $signatures = array(); + foreach ($method->getParameters() as $parameter) { + $signature = ''; + $type = $parameter->getClass(); + if (is_null($type) && version_compare(phpversion(), '5.1.0', '>=') && $parameter->isArray()) { + $signature .= 'array '; + } elseif (!is_null($type)) { + $signature .= $type->getName() . ' '; + } + if ($parameter->isPassedByReference()) { + $signature .= '&'; + } + $signature .= '$' . $this->suppressSpurious($parameter->getName()); + if ($this->isOptional($parameter)) { + $signature .= ' = null'; + } + $signatures[] = $signature; + } + return $signatures; + } + + /** + * The SPL library has problems with the + * Reflection library. In particular, you can + * get extra characters in parameter names :(. + * @param string $name Parameter name. + * @return string Cleaner name. + * @access private + */ + protected function suppressSpurious($name) { + return str_replace(array('[', ']', ' '), '', $name); + } + + /** + * Test of a reflection parameter being optional + * that works with early versions of PHP5. + * @param reflectionParameter $parameter Is this optional. + * @return boolean True if optional. + * @access private + */ + protected function isOptional($parameter) { + if (method_exists($parameter, 'isOptional')) { + return $parameter->isOptional(); + } + return false; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/remote.php b/lib/Swift/test-suite/lib/simpletest/remote.php new file mode 100644 index 0000000..43cc40f --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/remote.php @@ -0,0 +1,115 @@ +url = $url; + $this->dry_url = $dry_url ? $dry_url : $url; + $this->size = false; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->url; + } + + /** + * Runs the top level test for this class. Currently + * reads the data as a single chunk. I'll fix this + * once I have added iteration to the browser. + * @param SimpleReporter $reporter Target of test results. + * @returns boolean True if no failures. + * @access public + */ + function run($reporter) { + $browser = $this->createBrowser(); + $xml = $browser->get($this->url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->url . ']'); + return false; + } + $parser = $this->createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->url . ']'); + return false; + } + return true; + } + + /** + * Creates a new web browser object for fetching + * the XML report. + * @return SimpleBrowser New browser. + * @access protected + */ + protected function createBrowser() { + return new SimpleBrowser(); + } + + /** + * Creates the XML parser. + * @param SimpleReporter $reporter Target of test results. + * @return SimpleTestXmlListener XML reader. + * @access protected + */ + protected function createParser($reporter) { + return new SimpleTestXmlParser($reporter); + } + + /** + * Accessor for the number of subtests. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + if ($this->size === false) { + $browser = $this->createBrowser(); + $xml = $browser->get($this->dry_url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->dry_url . ']'); + return false; + } + $reporter = new SimpleReporter(); + $parser = $this->createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->dry_url . ']'); + return false; + } + $this->size = $reporter->getTestCaseCount(); + } + return $this->size; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/reporter.php b/lib/Swift/test-suite/lib/simpletest/reporter.php new file mode 100644 index 0000000..aaed016 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/reporter.php @@ -0,0 +1,446 @@ +character_set = $character_set; + } + + /** + * Paints the top of the web page setting the + * title to the name of the starting test. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + $this->sendNoCacheHeaders(); + print ""; + print "\n\n$test_name\n"; + print "\n"; + print "\n"; + print "\n\n"; + print "

$test_name

\n"; + flush(); + } + + /** + * Send the headers necessary to ensure the page is + * reloaded on every request. Otherwise you could be + * scratching your head over out of date test data. + * @access public + */ + static function sendNoCacheHeaders() { + if (! headers_sent()) { + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + } + } + + /** + * Paints the CSS. Add additional styles here. + * @return string CSS code as text. + * @access protected + */ + protected function getCss() { + return ".fail { background-color: inherit; color: red; }" . + ".pass { background-color: inherit; color: green; }" . + " pre { background-color: lightgray; color: inherit; }"; + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green"); + print "
"; + print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount(); + print " test cases complete:\n"; + print "" . $this->getPassCount() . " passes, "; + print "" . $this->getFailCount() . " fails and "; + print "" . $this->getExceptionCount() . " exceptions."; + print "
\n"; + print "\n\n"; + } + + /** + * Paints the test failure with a breadcrumbs + * trail of the nesting test suites below the + * top level test. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print "Fail: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
\n"; + } + + /** + * Paints a PHP error. + * @param string $message Message is ignored. + * @access public + */ + function paintError($message) { + parent::paintError($message); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
\n"; + } + + /** + * Paints a PHP exception. + * @param Exception $exception Exception to display. + * @access public + */ + function paintException($exception) { + parent::paintException($exception); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print " -> " . $this->htmlEntities($message) . "
\n"; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print "Skipped: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
\n"; + } + + /** + * Paints formatted text such as dumped privateiables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + print '
' . $this->htmlEntities($message) . '
'; + } + + /** + * Character set adjusted entity conversion. + * @param string $message Plain text or Unicode message. + * @return string Browser readable message. + * @access protected + */ + protected function htmlEntities($message) { + return htmlentities($message, ENT_COMPAT, $this->character_set); + } +} + +/** + * Sample minimal test displayer. Generates only + * failure messages and a pass count. For command + * line use. I've tried to make it look like JUnit, + * but I wanted to output the errors as they arrived + * which meant dropping the dots. + * @package SimpleTest + * @subpackage UnitTester + */ +class TextReporter extends SimpleReporter { + + /** + * Does nothing yet. The first output will + * be sent on the first test start. + * @access public + */ + function __construct() { + parent::__construct(); + } + + /** + * Paints the title only. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + if (! SimpleReporter::inCli()) { + header('Content-type: text/plain'); + } + print "$test_name\n"; + flush(); + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + if ($this->getFailCount() + $this->getExceptionCount() == 0) { + print "OK\n"; + } else { + print "FAILURES!!!\n"; + } + print "Test cases run: " . $this->getTestCaseProgress() . + "/" . $this->getTestCaseCount() . + ", Passes: " . $this->getPassCount() . + ", Failures: " . $this->getFailCount() . + ", Exceptions: " . $this->getExceptionCount() . "\n"; + } + + /** + * Paints the test failure as a stack trace. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print $this->getFailCount() . ") $message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Paints a PHP error or exception. + * @param string $message Message to be shown. + * @access public + * @abstract + */ + function paintError($message) { + parent::paintError($message); + print "Exception " . $this->getExceptionCount() . "!\n$message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Paints a PHP error or exception. + * @param Exception $exception Exception to describe. + * @access public + * @abstract + */ + function paintException($exception) { + parent::paintException($exception); + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print "Exception " . $this->getExceptionCount() . "!\n$message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print "Skip: $message\n"; + } + + /** + * Paints formatted text such as dumped privateiables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + print "$message\n"; + flush(); + } +} + +/** + * Runs just a single test group, a single case or + * even a single test within that case. + * @package SimpleTest + * @subpackage UnitTester + */ +class SelectiveReporter extends SimpleReporterDecorator { + private $just_this_case = false; + private $just_this_test = false; + private $on; + + /** + * Selects the test case or group to be run, + * and optionally a specific test. + * @param SimpleScorer $reporter Reporter to receive events. + * @param string $just_this_case Only this case or group will run. + * @param string $just_this_test Only this test method will run. + */ + function __construct($reporter, $just_this_case = false, $just_this_test = false) { + if (isset($just_this_case) && $just_this_case) { + $this->just_this_case = strtolower($just_this_case); + $this->off(); + } else { + $this->on(); + } + if (isset($just_this_test) && $just_this_test) { + $this->just_this_test = strtolower($just_this_test); + } + parent::__construct($reporter); + } + + /** + * Compares criteria to actual the case/group name. + * @param string $test_case The incoming test. + * @return boolean True if matched. + * @access protected + */ + protected function matchesTestCase($test_case) { + return $this->just_this_case == strtolower($test_case); + } + + /** + * Compares criteria to actual the test name. If no + * name was specified at the beginning, then all tests + * can run. + * @param string $method The incoming test method. + * @return boolean True if matched. + * @access protected + */ + protected function shouldRunTest($test_case, $method) { + if ($this->isOn() || $this->matchesTestCase($test_case)) { + if ($this->just_this_test) { + return $this->just_this_test == strtolower($method); + } else { + return true; + } + } + return false; + } + + /** + * Switch on testing for the group or subgroup. + * @access private + */ + protected function on() { + $this->on = true; + } + + /** + * Switch off testing for the group or subgroup. + * @access private + */ + protected function off() { + $this->on = false; + } + + /** + * Is this group actually being tested? + * @return boolean True if the current test group is active. + * @access private + */ + protected function isOn() { + return $this->on; + } + + /** + * Veto everything that doesn't match the method wanted. + * @param string $test_case Name of test case. + * @param string $method Name of test method. + * @return boolean True if test should be run. + * @access public + */ + function shouldInvoke($test_case, $method) { + if ($this->shouldRunTest($test_case, $method)) { + return $this->reporter->shouldInvoke($test_case, $method); + } + return false; + } + + /** + * Paints the start of a group test. + * @param string $test_case Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_case, $size) { + if ($this->just_this_case && $this->matchesTestCase($test_case)) { + $this->on(); + } + $this->reporter->paintGroupStart($test_case, $size); + } + + /** + * Paints the end of a group test. + * @param string $test_case Name of test or other label. + * @access public + */ + function paintGroupEnd($test_case) { + $this->reporter->paintGroupEnd($test_case); + if ($this->just_this_case && $this->matchesTestCase($test_case)) { + $this->off(); + } + } +} + +/** + * Suppresses skip messages. + * @package SimpleTest + * @subpackage UnitTester + */ +class NoSkipsReporter extends SimpleReporterDecorator { + + /** + * Does nothing. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/scorer.php b/lib/Swift/test-suite/lib/simpletest/scorer.php new file mode 100644 index 0000000..fa7c543 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/scorer.php @@ -0,0 +1,862 @@ +passes = 0; + $this->fails = 0; + $this->exceptions = 0; + $this->is_dry_run = false; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + $this->is_dry_run = $is_dry; + } + + /** + * The reporter has a veto on what should be run. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + return ! $this->is_dry_run; + } + + /** + * Can wrap the invoker in preperation for running + * a test. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + return $invoker; + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * Used for command line tools. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + if ($this->exceptions + $this->fails > 0) { + return false; + } + return true; + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + } + + /** + * Increments the pass count. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->passes++; + } + + /** + * Increments the fail count. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->fails++; + } + + /** + * Deals with PHP 4 throwing an error. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->exceptions++; + } + + /** + * Deals with PHP 5 throwing an exception. + * @param Exception $exception The actual exception thrown. + * @access public + */ + function paintException($exception) { + $this->exceptions++; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + } + + /** + * Accessor for the number of passes so far. + * @return integer Number of passes. + * @access public + */ + function getPassCount() { + return $this->passes; + } + + /** + * Accessor for the number of fails so far. + * @return integer Number of fails. + * @access public + */ + function getFailCount() { + return $this->fails; + } + + /** + * Accessor for the number of untrapped errors + * so far. + * @return integer Number of exceptions. + * @access public + */ + function getExceptionCount() { + return $this->exceptions; + } + + /** + * Paints a simple supplementary message. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + } + + /** + * Paints a formatted ASCII message such as a + * privateiable dump. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + } + + /** + * By default just ignores user generated events. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @access public + */ + function paintSignal($type, $payload) { + } +} + +/** + * Recipient of generated test messages that can display + * page footers and headers. Also keeps track of the + * test nesting. This is the main base class on which + * to build the finished test (page based) displays. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleReporter extends SimpleScorer { + private $test_stack; + private $size; + private $progress; + + /** + * Starts the display with no results in. + * @access public + */ + function __construct() { + parent::__construct(); + $this->test_stack = array(); + $this->size = null; + $this->progress = 0; + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Paints the start of a group test. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + if (! isset($this->size)) { + $this->size = $size; + } + if (count($this->test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a group test. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @param integer $progress Number of test cases ending. + * @access public + */ + function paintGroupEnd($test_name) { + array_pop($this->test_stack); + if (count($this->test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test case. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + if (! isset($this->size)) { + $this->size = 1; + } + if (count($this->test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a test case. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintCaseEnd($test_name) { + $this->progress++; + array_pop($this->test_stack); + if (count($this->test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a test method. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintMethodEnd($test_name) { + array_pop($this->test_stack); + } + + /** + * Paints the test document header. + * @param string $test_name First test top level + * to start. + * @access public + * @abstract + */ + function paintHeader($test_name) { + } + + /** + * Paints the test document footer. + * @param string $test_name The top level test. + * @access public + * @abstract + */ + function paintFooter($test_name) { + } + + /** + * Accessor for internal test stack. For + * subclasses that need to see the whole test + * history for display purposes. + * @return array List of methods in nesting order. + * @access public + */ + function getTestList() { + return $this->test_stack; + } + + /** + * Accessor for total test size in number + * of test cases. Null until the first + * test is started. + * @return integer Total number of cases at start. + * @access public + */ + function getTestCaseCount() { + return $this->size; + } + + /** + * Accessor for the number of test cases + * completed so far. + * @return integer Number of ended cases. + * @access public + */ + function getTestCaseProgress() { + return $this->progress; + } + + /** + * Static check for running in the comand line. + * @return boolean True if CLI. + * @access public + */ + static function inCli() { + return php_sapi_name() == 'cli'; + } +} + +/** + * For modifying the behaviour of the visual reporters. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleReporterDecorator { + protected $reporter; + + /** + * Mediates between the reporter and the test case. + * @param SimpleScorer $reporter Reporter to receive events. + */ + function __construct($reporter) { + $this->reporter = $reporter; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + $this->reporter->makeDry($is_dry); + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * Used for command line tools. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + return $this->reporter->getStatus(); + } + + /** + * The reporter has a veto on what should be run. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @return boolean True if test should be run. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + return $this->reporter->shouldInvoke($test_case_name, $method); + } + + /** + * Can wrap the invoker in preperation for running + * a test. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + return $this->reporter->createInvoker($invoker); + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return $this->reporter->getDumper(); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + $this->reporter->paintGroupStart($test_name, $size); + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + $this->reporter->paintGroupEnd($test_name); + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + $this->reporter->paintCaseStart($test_name); + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + $this->reporter->paintCaseEnd($test_name); + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + $this->reporter->paintMethodStart($test_name); + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + $this->reporter->paintMethodEnd($test_name); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->reporter->paintPass($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->reporter->paintFail($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->reporter->paintError($message); + } + + /** + * Chains to the wrapped reporter. + * @param Exception $exception Exception to show. + * @access public + */ + function paintException($exception) { + $this->reporter->paintException($exception); + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + $this->reporter->paintSkip($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + $this->reporter->paintMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + $this->reporter->paintFormattedMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @return boolean Should return false if this + * type of signal should fail the + * test suite. + * @access public + */ + function paintSignal($type, $payload) { + $this->reporter->paintSignal($type, $payload); + } +} + +/** + * For sending messages to multiple reporters at + * the same time. + * @package SimpleTest + * @subpackage UnitTester + */ +class MultipleReporter { + private $reporters = array(); + + /** + * Adds a reporter to the subscriber list. + * @param SimpleScorer $reporter Reporter to receive events. + * @access public + */ + function attachReporter($reporter) { + $this->reporters[] = $reporter; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->makeDry($is_dry); + } + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * If any reporter reports a failure, the whole + * suite fails. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + for ($i = 0; $i < count($this->reporters); $i++) { + if (! $this->reporters[$i]->getStatus()) { + return false; + } + } + return true; + } + + /** + * The reporter has a veto on what should be run. + * It requires all reporters to want to run the method. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + for ($i = 0; $i < count($this->reporters); $i++) { + if (! $this->reporters[$i]->shouldInvoke($test_case_name, $method)) { + return false; + } + } + return true; + } + + /** + * Every reporter gets a chance to wrap the invoker. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + for ($i = 0; $i < count($this->reporters); $i++) { + $invoker = $this->reporters[$i]->createInvoker($invoker); + } + return $invoker; + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintGroupStart($test_name, $size); + } + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintGroupEnd($test_name); + } + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintCaseStart($test_name); + } + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintCaseEnd($test_name); + } + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMethodStart($test_name); + } + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMethodEnd($test_name); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintPass($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintFail($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintError($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param Exception $exception Exception to display. + * @access public + */ + function paintException($exception) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintException($exception); + } + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintSkip($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMessage($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintFormattedMessage($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @return boolean Should return false if this + * type of signal should fail the + * test suite. + * @access public + */ + function paintSignal($type, $payload) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintSignal($type, $payload); + } + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/selector.php b/lib/Swift/test-suite/lib/simpletest/selector.php new file mode 100644 index 0000000..ba2fed3 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/selector.php @@ -0,0 +1,141 @@ +name = $name; + } + + /** + * Accessor for name. + * @returns string $name Name to match. + */ + function getName() { + return $this->name; + } + + /** + * Compares with name attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return ($widget->getName() == $this->name); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabel { + private $label; + + /** + * Stashes the name for later comparison. + * @param string $label Visible text to match. + */ + function __construct($label) { + $this->label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (! method_exists($widget, 'isLabel')) { + return false; + } + return $widget->isLabel($this->label); + } +} + +/** + * Used to extract form elements for testing against. + * Searches dy id attribute. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleById { + private $id; + + /** + * Stashes the name for later comparison. + * @param string $id ID atribute to match. + */ + function __construct($id) { + $this->id = $id; + } + + /** + * Comparison. Compares id attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return $widget->isId($this->id); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label, name or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabelOrName { + private $label; + + /** + * Stashes the name/label for later comparison. + * @param string $label Visible text to match. + */ + function __construct($label) { + $this->label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label or name. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (method_exists($widget, 'isLabel')) { + if ($widget->isLabel($this->label)) { + return true; + } + } + return ($widget->getName() == $this->label); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/shell_tester.php b/lib/Swift/test-suite/lib/simpletest/shell_tester.php new file mode 100644 index 0000000..b9d3762 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/shell_tester.php @@ -0,0 +1,330 @@ +output = false; + } + + /** + * Actually runs the command. Does not trap the + * error stream output as this need PHP 4.3+. + * @param string $command The actual command line + * to run. + * @return integer Exit code. + * @access public + */ + function execute($command) { + $this->output = false; + exec($command, $this->output, $ret); + return $ret; + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + return implode("\n", $this->output); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + return $this->output; + } +} + +/** + * Test case for testing of command line scripts and + * utilities. Usually scripts that are external to the + * PHP code, but support it in some way. + * @package SimpleTest + * @subpackage UnitTester + */ +class ShellTestCase extends SimpleTestCase { + private $current_shell; + private $last_status; + private $last_command; + + /** + * Creates an empty test case. Should be subclassed + * with test methods for a functional test case. + * @param string $label Name of test case. Will use + * the class name if none specified. + * @access public + */ + function __construct($label = false) { + parent::__construct($label); + $this->current_shell = $this->createShell(); + $this->last_status = false; + $this->last_command = ''; + } + + /** + * Executes a command and buffers the results. + * @param string $command Command to run. + * @return boolean True if zero exit code. + * @access public + */ + function execute($command) { + $shell = $this->getShell(); + $this->last_status = $shell->execute($command); + $this->last_command = $command; + return ($this->last_status === 0); + } + + /** + * Dumps the output of the last command. + * @access public + */ + function dumpOutput() { + $this->dump($this->getOutput()); + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + $shell = $this->getShell(); + return $shell->getOutput(); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + $shell = $this->getShell(); + return $shell->getOutputAsList(); + } + + /** + * Called from within the test methods to register + * passes and failures. + * @param boolean $result Pass on true. + * @param string $message Message to display describing + * the test state. + * @return boolean True on pass + * @access public + */ + function assertTrue($result, $message = false) { + return $this->assert(new TrueExpectation(), $result, $message); + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = '%s') { + return $this->assert(new FalseExpectation(), $result, $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = "%s") { + return $this->assert( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = "%s") { + return $this->assert( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Tests the last status code from the shell. + * @param integer $status Expected status of last + * command. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertExitCode($status, $message = "%s") { + $message = sprintf($message, "Expected status code of [$status] from [" . + $this->last_command . "], but got [" . + $this->last_status . "]"); + return $this->assertTrue($status === $this->last_status, $message); + } + + /** + * Attempt to exactly match the combined STDERR and + * STDOUT output. + * @param string $expected Expected output. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutput($expected, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new EqualExpectation($expected), + $shell->getOutput(), + $message); + } + + /** + * Scans the output for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutputPattern($pattern, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new PatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * If a Perl regex is found anywhere in the current + * output then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoOutputPattern($pattern, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new NoPatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * File existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should exist"); + return $this->assertTrue(file_exists($path), $message); + } + + /** + * File non-existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileNotExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should not exist"); + return $this->assertFalse(file_exists($path), $message); + } + + /** + * Scans a file for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFilePattern($pattern, $path, $message = "%s") { + return $this->assert( + new PatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * If a Perl regex is found anywhere in the named + * file then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoFilePattern($pattern, $path, $message = "%s") { + return $this->assert( + new NoPatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * Accessor for current shell. Used for testing the + * the tester itself. + * @return Shell Current shell. + * @access protected + */ + protected function getShell() { + return $this->current_shell; + } + + /** + * Factory for the shell to run the command on. + * @return Shell New shell object. + * @access protected + */ + protected function createShell() { + return new SimpleShell(); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/simpletest.php b/lib/Swift/test-suite/lib/simpletest/simpletest.php new file mode 100644 index 0000000..cfdadf2 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/simpletest.php @@ -0,0 +1,396 @@ += 0) { + require_once(dirname(__FILE__) . '/reflection_php5.php'); +} else { + require_once(dirname(__FILE__) . '/reflection_php4.php'); +} +require_once(dirname(__FILE__) . '/default_reporter.php'); +require_once(dirname(__FILE__) . '/compatibility.php'); +/**#@-*/ + +/** + * Registry and test context. Includes a few + * global options that I'm slowly getting rid of. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleTest { + + /** + * Reads the SimpleTest version from the release file. + * @return string Version string. + * @access public + */ + static function getVersion() { + $content = file(dirname(__FILE__) . '/VERSION'); + return trim($content[0]); + } + + /** + * Sets the name of a test case to ignore, usually + * because the class is an abstract case that should + * not be run. Once PHP4 is dropped this will disappear + * as a public method and "abstract" will rule. + * @param string $class Add a class to ignore. + * @access public + */ + static function ignore($class) { + $registry = &SimpleTest::getRegistry(); + $registry['IgnoreList'][strtolower($class)] = true; + } + + /** + * Scans the now complete ignore list, and adds + * all parent classes to the list. If a class + * is not a runnable test case, then it's parents + * wouldn't be either. This is syntactic sugar + * to cut down on ommissions of ignore()'s or + * missing abstract declarations. This cannot + * be done whilst loading classes wiithout forcing + * a particular order on the class declarations and + * the ignore() calls. It's just nice to have the ignore() + * calls at the top of the file before the actual declarations. + * @param array $classes Class names of interest. + * @access public + */ + static function ignoreParentsIfIgnored($classes) { + $registry = &SimpleTest::getRegistry(); + foreach ($classes as $class) { + if (SimpleTest::isIgnored($class)) { + $reflection = new SimpleReflection($class); + if ($parent = $reflection->getParent()) { + SimpleTest::ignore($parent); + } + } + } + } + + /** + * Puts the object to the global pool of 'preferred' objects + * which can be retrieved with SimpleTest :: preferred() method. + * Instances of the same class are overwritten. + * @param object $object Preferred object + * @access public + * @see preferred() + */ + static function prefer($object) { + $registry = &SimpleTest::getRegistry(); + $registry['Preferred'][] = $object; + } + + /** + * Retrieves 'preferred' objects from global pool. Class filter + * can be applied in order to retrieve the object of the specific + * class + * @param array|string $classes Allowed classes or interfaces. + * @access public + * @return array|object|null + * @see prefer() + */ + static function preferred($classes) { + if (! is_array($classes)) { + $classes = array($classes); + } + $registry = &SimpleTest::getRegistry(); + for ($i = count($registry['Preferred']) - 1; $i >= 0; $i--) { + foreach ($classes as $class) { + if (SimpleTestCompatibility::isA($registry['Preferred'][$i], $class)) { + return $registry['Preferred'][$i]; + } + } + } + return null; + } + + /** + * Test to see if a test case is in the ignore + * list. Quite obviously the ignore list should + * be a separate object and will be one day. + * This method is internal to SimpleTest. Don't + * use it. + * @param string $class Class name to test. + * @return boolean True if should not be run. + * @access public + */ + static function isIgnored($class) { + $registry = &SimpleTest::getRegistry(); + return isset($registry['IgnoreList'][strtolower($class)]); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set host + * to false to disable. This will take effect + * if there are no other proxy settings. + * @param string $proxy Proxy host as URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + static function useProxy($proxy, $username = false, $password = false) { + $registry = &SimpleTest::getRegistry(); + $registry['DefaultProxy'] = $proxy; + $registry['DefaultProxyUsername'] = $username; + $registry['DefaultProxyPassword'] = $password; + } + + /** + * Accessor for default proxy host. + * @return string Proxy URL. + * @access public + */ + static function getDefaultProxy() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxy']; + } + + /** + * Accessor for default proxy username. + * @return string Proxy username for authentication. + * @access public + */ + static function getDefaultProxyUsername() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxyUsername']; + } + + /** + * Accessor for default proxy password. + * @return string Proxy password for authentication. + * @access public + */ + static function getDefaultProxyPassword() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxyPassword']; + } + + /** + * Accessor for global registry of options. + * @return hash All stored values. + * @access private + */ + protected static function &getRegistry() { + static $registry = false; + if (! $registry) { + $registry = SimpleTest::getDefaults(); + } + return $registry; + } + + /** + * Accessor for the context of the current + * test run. + * @return SimpleTestContext Current test run. + * @access public + */ + static function getContext() { + static $context = false; + if (! $context) { + $context = new SimpleTestContext(); + } + return $context; + } + + /** + * Constant default values. + * @return hash All registry defaults. + * @access private + */ + protected static function getDefaults() { + return array( + 'MockBaseClass' => 'SimpleMock', + 'IgnoreList' => array(), + 'DefaultProxy' => false, + 'DefaultProxyUsername' => false, + 'DefaultProxyPassword' => false, + 'Preferred' => array(new HtmlReporter(), new TextReporter(), new XmlReporter())); + } + + /** + * @deprecated + */ + static function setMockBaseClass($mock_base) { + $registry = &SimpleTest::getRegistry(); + $registry['MockBaseClass'] = $mock_base; + } + + /** + * @deprecated + */ + static function getMockBaseClass() { + $registry = &SimpleTest::getRegistry(); + return $registry['MockBaseClass']; + } +} + +/** + * Container for all components for a specific + * test run. Makes things like error queues + * available to PHP event handlers, and also + * gets around some nasty reference issues in + * the mocks. + * @package SimpleTest + */ +class SimpleTestContext { + private $test; + private $reporter; + private $resources; + + /** + * Clears down the current context. + * @access public + */ + function clear() { + $this->resources = array(); + } + + /** + * Sets the current test case instance. This + * global instance can be used by the mock objects + * to send message to the test cases. + * @param SimpleTestCase $test Test case to register. + * @access public + */ + function setTest($test) { + $this->clear(); + $this->test = $test; + } + + /** + * Accessor for currently running test case. + * @return SimpleTestCase Current test. + * @access public + */ + function getTest() { + return $this->test; + } + + /** + * Sets the current reporter. This + * global instance can be used by the mock objects + * to send messages. + * @param SimpleReporter $reporter Reporter to register. + * @access public + */ + function setReporter($reporter) { + $this->clear(); + $this->reporter = $reporter; + } + + /** + * Accessor for current reporter. + * @return SimpleReporter Current reporter. + * @access public + */ + function getReporter() { + return $this->reporter; + } + + /** + * Accessor for the Singleton resource. + * @return object Global resource. + * @access public + */ + function get($resource) { + if (! isset($this->resources[$resource])) { + $this->resources[$resource] = new $resource(); + } + return $this->resources[$resource]; + } +} + +/** + * Interrogates the stack trace to recover the + * failure point. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleStackTrace { + private $prefixes; + + /** + * Stashes the list of target prefixes. + * @param array $prefixes List of method prefixes + * to search for. + */ + function __construct($prefixes) { + $this->prefixes = $prefixes; + } + + /** + * Extracts the last method name that was not within + * Simpletest itself. Captures a stack trace if none given. + * @param array $stack List of stack frames. + * @return string Snippet of test report with line + * number and file. + * @access public + */ + function traceMethod($stack = false) { + $stack = $stack ? $stack : $this->captureTrace(); + foreach ($stack as $frame) { + if ($this->frameLiesWithinSimpleTestFolder($frame)) { + continue; + } + if ($this->frameMatchesPrefix($frame)) { + return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']'; + } + } + return ''; + } + + /** + * Test to see if error is generated by SimpleTest itself. + * @param array $frame PHP stack frame. + * @return boolean True if a SimpleTest file. + * @access private + */ + protected function frameLiesWithinSimpleTestFolder($frame) { + if (isset($frame['file'])) { + $path = substr(SIMPLE_TEST, 0, -1); + if (strpos($frame['file'], $path) === 0) { + if (dirname($frame['file']) == $path) { + return true; + } + } + } + return false; + } + + /** + * Tries to determine if the method call is an assert, etc. + * @param array $frame PHP stack frame. + * @return boolean True if matches a target. + * @access private + */ + protected function frameMatchesPrefix($frame) { + foreach ($this->prefixes as $prefix) { + if (strncmp($frame['function'], $prefix, strlen($prefix)) == 0) { + return true; + } + } + return false; + } + + /** + * Grabs a current stack trace. + * @return array Fulle trace. + * @access private + */ + protected function captureTrace() { + if (function_exists('debug_backtrace')) { + return array_reverse(debug_backtrace()); + } + return array(); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/socket.php b/lib/Swift/test-suite/lib/simpletest/socket.php new file mode 100644 index 0000000..c30c877 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/socket.php @@ -0,0 +1,308 @@ +clearError(); + } + + /** + * Test for an outstanding error. + * @return boolean True if there is an error. + * @access public + */ + function isError() { + return ($this->error != ''); + } + + /** + * Accessor for an outstanding error. + * @return string Empty string if no error otherwise + * the error message. + * @access public + */ + function getError() { + return $this->error; + } + + /** + * Sets the internal error. + * @param string Error message to stash. + * @access protected + */ + function setError($error) { + $this->error = $error; + } + + /** + * Resets the error state to no error. + * @access protected + */ + function clearError() { + $this->setError(''); + } +} + +class SimpleFileSocket extends SimpleStickyError { + private $handle; + private $is_open = false; + private $sent = ''; + private $block_size; + + /** + * Opens a socket for reading and writing. + * @param SimpleUrl $file Target URI to fetch. + * @param integer $block_size Size of chunk to read. + * @access public + */ + function __construct($file, $block_size = 1024) { + parent::__construct(); + if (! ($this->handle = $this->openFile($file, $error))) { + $file_string = $file->asString(); + $this->setError("Cannot open [$file_string] with [$error]"); + return; + } + $this->is_open = true; + $this->block_size = $block_size; + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + return true; + } + + /** + * Reads data from the socket. The error suppresion + * is a workaround for PHP4 always throwing a warning + * with a secure socket. + * @return integer/boolean Incoming bytes. False + * on error. + * @access public + */ + function read() { + $raw = @fread($this->handle, $this->block_size); + if ($raw === false) { + $this->setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + if (!$this->is_open) return false; + $this->is_open = false; + return fclose($this->handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Actually opens the low level socket. + * @param SimpleUrl $file SimpleUrl file target. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + protected function openFile($file, &$error) { + return @fopen($file->asString(), 'r'); + } +} + +/** + * Wrapper for TCP/IP socket. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSocket extends SimpleStickyError { + private $handle; + private $is_open = false; + private $sent = ''; + private $lock_size; + + /** + * Opens a socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @param integer $block_size Size of chunk to read. + * @access public + */ + function __construct($host, $port, $timeout, $block_size = 255) { + parent::__construct(); + if (! ($this->handle = $this->openSocket($host, $port, $error_number, $error, $timeout))) { + $this->setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds"); + return; + } + $this->is_open = true; + $this->block_size = $block_size; + SimpleTestCompatibility::setTimeout($this->handle, $timeout); + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $count = fwrite($this->handle, $message); + if (! $count) { + if ($count === false) { + $this->setError('Cannot write to socket'); + $this->close(); + } + return false; + } + fflush($this->handle); + $this->sent .= $message; + return true; + } + + /** + * Reads data from the socket. The error suppresion + * is a workaround for PHP4 always throwing a warning + * with a secure socket. + * @return integer/boolean Incoming bytes. False + * on error. + * @access public + */ + function read() { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $raw = @fread($this->handle, $this->block_size); + if ($raw === false) { + $this->setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + $this->is_open = false; + return fclose($this->handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipoent of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + protected function openSocket($host, $port, &$error_number, &$error, $timeout) { + return @fsockopen($host, $port, $error_number, $error, $timeout); + } +} + +/** + * Wrapper for TCP/IP socket over TLS. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSecureSocket extends SimpleSocket { + + /** + * Opens a secure socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @access public + */ + function __construct($host, $port, $timeout) { + parent::__construct($host, $port, $timeout); + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + function openSocket($host, $port, &$error_number, &$error, $timeout) { + return parent::openSocket("tls://$host", $port, $error_number, $error, $timeout); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/tag.php b/lib/Swift/test-suite/lib/simpletest/tag.php new file mode 100644 index 0000000..c30417d --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/tag.php @@ -0,0 +1,1418 @@ +name = strtolower(trim($name)); + $this->attributes = $attributes; + $this->content = ''; + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * The current tag should not swallow all content for + * itself as it's searchable page content. Private + * content tags are usually widgets that contain default + * values. + * @return boolean False as content is available + * to other tags by default. + * @access public + */ + function isPrivateContent() { + return false; + } + + /** + * Appends string content to the current content. + * @param string $content Additional text. + * @access public + */ + function addContent($content) { + $this->content .= (string)$content; + } + + /** + * Adds an enclosed tag to the content. + * @param SimpleTag $tag New tag. + * @access public + */ + function addTag($tag) { + } + + /** + * Accessor for tag name. + * @return string Name of tag. + * @access public + */ + function getTagName() { + return $this->name; + } + + /** + * List of legal child elements. + * @return array List of element names. + * @access public + */ + function getChildElements() { + return array(); + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return string Attribute value. + * @access public + */ + function getAttribute($label) { + $label = strtolower($label); + if (! isset($this->attributes[$label])) { + return false; + } + return (string)$this->attributes[$label]; + } + + /** + * Sets an attribute. + * @param string $label Attribute name. + * @return string $value New attribute value. + * @access protected + */ + protected function setAttribute($label, $value) { + $this->attributes[strtolower($label)] = $value; + } + + /** + * Accessor for the whole content so far. + * @return string Content as big raw string. + * @access public + */ + function getContent() { + return $this->content; + } + + /** + * Accessor for content reduced to visible text. Acts + * like a text mode browser, normalising space and + * reducing images to their alt text. + * @return string Content as plain text. + * @access public + */ + function getText() { + return SimpleHtmlSaxParser::normalise($this->content); + } + + /** + * Test to see if id attribute matches. + * @param string $id ID to test against. + * @return boolean True on match. + * @access public + */ + function isId($id) { + return ($this->getAttribute('id') == $id); + } +} + +/** + * Base url. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleBaseTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('base', $attributes); + } + + /** + * Base tag is not a block tag. + * @return boolean false + * @access public + */ + function expectEndTag() { + return false; + } +} + +/** + * Page title. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTitleTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('title', $attributes); + } +} + +/** + * Link. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAnchorTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('a', $attributes); + } + + /** + * Accessor for URL as string. + * @return string Coerced as string. + * @access public + */ + function getHref() { + $url = $this->getAttribute('href'); + if (is_bool($url)) { + $url = ''; + } + return $url; + } +} + +/** + * Form element. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleWidget extends SimpleTag { + private $value; + private $label; + private $is_set; + + /** + * Starts with a named tag with attributes only. + * @param string $name Tag name. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($name, $attributes) { + parent::__construct($name, $attributes); + $this->value = false; + $this->label = false; + $this->is_set = false; + } + + /** + * Accessor for name submitted as the key in + * GET/POST privateiables hash. + * @return string Parsed value. + * @access public + */ + function getName() { + return $this->getAttribute('name'); + } + + /** + * Accessor for default value parsed with the tag. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->getAttribute('value'); + } + + /** + * Accessor for currently set value or default if + * none. + * @return string Value set by form or default + * if none. + * @access public + */ + function getValue() { + if (! $this->is_set) { + return $this->getDefault(); + } + return $this->value; + } + + /** + * Sets the current form element value. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + $this->value = $value; + $this->is_set = true; + return true; + } + + /** + * Resets the form element value back to the + * default. + * @access public + */ + function resetValue() { + $this->is_set = false; + } + + /** + * Allows setting of a label externally, say by a + * label tag. + * @param string $label Label to attach. + * @access public + */ + function setLabel($label) { + $this->label = trim($label); + } + + /** + * Reads external or internal label. + * @param string $label Label to test. + * @return boolean True is match. + * @access public + */ + function isLabel($label) { + return $this->label == trim($label); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + if ($this->getName()) { + $encoding->add($this->getName(), $this->getValue()); + } + } +} + +/** + * Text, password and hidden field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', ''); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Sets the current form element value. Cannot + * change the value of a hidden field. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($this->getAttribute('type') == 'hidden') { + return false; + } + return parent::setValue($value); + } +} + +/** + * Submit button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'Submit'); + } + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getValue(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Image button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleImageSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + if ($this->getAttribute('title')) { + return $this->getAttribute('title'); + } + return $this->getAttribute('alt'); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @param integer $x X coordinate of click. + * @param integer $y Y coordinate of click. + * @access public + */ + function write($encoding, $x = 1, $y = 1) { + if ($this->getName()) { + $encoding->add($this->getName() . '.x', $x); + $encoding->add($this->getName() . '.y', $y); + } else { + $encoding->add('x', $x); + $encoding->add('y', $y); + } + } +} + +/** + * Submit button as button tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleButtonTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * Defaults are very browser dependent. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('button', $attributes); + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getContent(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Content tag for text area. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextAreaTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('textarea', $attributes); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->wrap(SimpleHtmlSaxParser::decodeHtml($this->getContent())); + } + + /** + * Applies word wrapping if needed. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return parent::setValue($this->wrap($value)); + } + + /** + * Test to see if text should be wrapped. + * @return boolean True if wrapping on. + * @access private + */ + function wrapIsEnabled() { + if ($this->getAttribute('cols')) { + $wrap = $this->getAttribute('wrap'); + if (($wrap == 'physical') || ($wrap == 'hard')) { + return true; + } + } + return false; + } + + /** + * Performs the formatting that is peculiar to + * this tag. There is strange behaviour in this + * one, including stripping a leading new line. + * Go figure. I am using Firefox as a guide. + * @param string $text Text to wrap. + * @return string Text wrapped with carriage + * returns and line feeds + * @access private + */ + protected function wrap($text) { + $text = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $text)); + $text = str_replace("\r\n\n", "\r\n", str_replace("\r", "\r\n", $text)); + if (strncmp($text, "\r\n", strlen("\r\n")) == 0) { + $text = substr($text, strlen("\r\n")); + } + if ($this->wrapIsEnabled()) { + return wordwrap( + $text, + (integer)$this->getAttribute('cols'), + "\r\n"); + } + return $text; + } + + /** + * The content of textarea is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * File upload widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleUploadTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + if (! file_exists($this->getValue())) { + return; + } + $encoding->attach( + $this->getName(), + implode('', file($this->getValue())), + basename($this->getValue())); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSelectionTag extends SimpleWidget { + private $options; + private $choice; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('select', $attributes); + $this->options = array(); + $this->choice = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag($tag) { + if ($tag->getTagName() == 'option') { + $this->options[] = $tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults. If none, then + * the first option is selected. + * @return string Selected field. + * @access public + */ + function getDefault() { + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->getAttribute('selected') !== false) { + return $this->options[$i]->getDefault(); + } + } + if ($count > 0) { + return $this->options[0]->getDefault(); + } + return ''; + } + + /** + * Can only set allowed values. + * @param string $value New choice. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->isValue($value)) { + $this->choice = $i; + return true; + } + } + return false; + } + + /** + * Accessor for current selection value. + * @return string Value attribute or + * content of opton. + * @access public + */ + function getValue() { + if ($this->choice === false) { + return $this->getDefault(); + } + return $this->options[$this->choice]->getValue(); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class MultipleSelectionTag extends SimpleWidget { + private $options; + private $values; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('select', $attributes); + $this->options = array(); + $this->values = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag($tag) { + if ($tag->getTagName() == 'option') { + $this->options[] = &$tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults to populate the + * value array(). + * @return array Selected fields. + * @access public + */ + function getDefault() { + $default = array(); + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->getAttribute('selected') !== false) { + $default[] = $this->options[$i]->getDefault(); + } + } + return $default; + } + + /** + * Can only set allowed values. Any illegal value + * will result in a failure, but all correct values + * will be set. + * @param array $desired New choices. + * @return boolean True if all allowed. + * @access public + */ + function setValue($desired) { + $achieved = array(); + foreach ($desired as $value) { + $success = false; + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->isValue($value)) { + $achieved[] = $this->options[$i]->getValue(); + $success = true; + break; + } + } + if (! $success) { + return false; + } + } + $this->values = $achieved; + return true; + } + + /** + * Accessor for current selection value. + * @return array List of currently set options. + * @access public + */ + function getValue() { + if ($this->values === false) { + return $this->getDefault(); + } + return $this->values; + } +} + +/** + * Option for selection field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleOptionTag extends SimpleWidget { + + /** + * Stashes the attributes. + */ + function __construct($attributes) { + parent::__construct('option', $attributes); + } + + /** + * Does nothing. + * @param string $value Ignored. + * @return boolean Not allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Test to see if a value matches the option. + * @param string $compare Value to compare with. + * @return boolean True if possible match. + * @access public + */ + function isValue($compare) { + $compare = trim($compare); + if (trim($this->getValue()) == $compare) { + return true; + } + return trim($this->getContent()) == $compare; + } + + /** + * Accessor for starting value. Will be set to + * the option label if no value exists. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('value') === false) { + return $this->getContent(); + } + return $this->getAttribute('value'); + } + + /** + * The content of options is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * Radio button. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioButtonTag extends SimpleWidget { + + /** + * Stashes the attributes. + * @param array $attributes Hash of attributes. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value sn the one in the + * "value" attribute. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * Checkbox widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value in the one in the + * "value" attribute. The default for this + * attribute is "on". If this widget is set to + * true, then the usual value will be taken. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value === true) { + return parent::setValue($this->getAttribute('value')); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. The default + * value is "on". + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * A group of multiple widgets with some shared behaviour. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTagGroup { + private $widgets = array(); + + /** + * Adds a tag to the group. + * @param SimpleWidget $widget + * @access public + */ + function addWidget($widget) { + $this->widgets[] = $widget; + } + + /** + * Accessor to widget set. + * @return array All widgets. + * @access protected + */ + protected function &getWidgets() { + return $this->widgets; + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return boolean Always false. + * @access public + */ + function getAttribute($label) { + return false; + } + + /** + * Fetches the name for the widget from the first + * member. + * @return string Name of widget. + * @access public + */ + function getName() { + if (count($this->widgets) > 0) { + return $this->widgets[0]->getName(); + } + } + + /** + * Scans the widgets for one with the appropriate + * ID field. + * @param string $id ID value to try. + * @return boolean True if matched. + * @access public + */ + function isId($id) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($this->widgets[$i]->isId($id)) { + return true; + } + } + return false; + } + + /** + * Scans the widgets for one with the appropriate + * attached label. + * @param string $label Attached label to try. + * @return boolean True if matched. + * @access public + */ + function isLabel($label) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($this->widgets[$i]->isLabel($label)) { + return true; + } + } + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + $encoding->add($this->getName(), $this->getValue()); + } +} + +/** + * A group of tags with the same name within a form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxGroup extends SimpleTagGroup { + + /** + * Accessor for current selected widget or false + * if none. + * @return string/array Widget values or false if none. + * @access public + */ + function getValue() { + $values = array(); + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + $values[] = $widgets[$i]->getValue(); + } + } + return $this->coerceValues($values); + } + + /** + * Accessor for starting value that is active. + * @return string/array Widget values or false if none. + * @access public + */ + function getDefault() { + $values = array(); + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + $values[] = $widgets[$i]->getDefault(); + } + } + return $this->coerceValues($values); + } + + /** + * Accessor for current set values. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean True if all values can be set. + * @access public + */ + function setValue($values) { + $values = $this->makeArray($values); + if (! $this->valuesArePossible($values)) { + return false; + } + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($widgets[$i]->getAttribute('value'), $values)) { + $widgets[$i]->setValue($possible); + } else { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a possible value set is legal. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean False if trying to set a + * missing value. + * @access private + */ + protected function valuesArePossible($values) { + $matches = array(); + $widgets = &$this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($possible, $values)) { + $matches[] = $possible; + } + } + return ($values == $matches); + } + + /** + * Converts the output to an appropriate format. This means + * that no values is false, a single value is just that + * value and only two or more are contained in an array. + * @param array $values List of values of widgets. + * @return string/array/boolean Expected format for a tag. + * @access private + */ + protected function coerceValues($values) { + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Converts false or string into array. The opposite of + * the coercian method. + * @param string/array/boolean $value A single item is converted + * to a one item list. False + * gives an empty list. + * @return array List of values, possibly empty. + * @access private + */ + protected function makeArray($value) { + if ($value === false) { + return array(); + } + if (is_string($value)) { + return array($value); + } + return $value; + } +} + +/** + * A group of tags with the same name within a form. + * Used for radio buttons. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioGroup extends SimpleTagGroup { + + /** + * Each tag is tried in turn until one is + * successfully set. The others will be + * unchecked if successful. + * @param string $value New value. + * @return boolean True if any allowed. + * @access public + */ + function setValue($value) { + if (! $this->valueIsPossible($value)) { + return false; + } + $index = false; + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if (! $widgets[$i]->setValue($value)) { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a value is allowed. + * @param string Attempted value. + * @return boolean True if a valid value. + * @access private + */ + protected function valueIsPossible($value) { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getAttribute('value') == $value) { + return true; + } + } + return false; + } + + /** + * Accessor for current selected widget or false + * if none. + * @return string/boolean Value attribute or + * content of opton. + * @access public + */ + function getValue() { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + return $widgets[$i]->getValue(); + } + } + return false; + } + + /** + * Accessor for starting value that is active. + * @return string/boolean Value of first checked + * widget or false if none. + * @access public + */ + function getDefault() { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + return $widgets[$i]->getDefault(); + } + } + return false; + } +} + +/** + * Tag to keep track of labels. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleLabelTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('label', $attributes); + } + + /** + * Access for the ID to attach the label to. + * @return string For attribute. + * @access public + */ + function getFor() { + return $this->getAttribute('for'); + } +} + +/** + * Tag to aid parsing the form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFormTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('form', $attributes); + } +} + +/** + * Tag to aid parsing the frames in a page. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFrameTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('frame', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/test_case.php b/lib/Swift/test-suite/lib/simpletest/test_case.php new file mode 100644 index 0000000..1666548 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/test_case.php @@ -0,0 +1,655 @@ +label = $label; + } + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->label ? $this->label : get_class($this); + } + + /** + * This is a placeholder for skipping tests. In this + * method you place skipIf() and skipUnless() calls to + * set the skipping state. + * @access public + */ + function skip() { + } + + /** + * Will issue a message to the reporter and tell the test + * case to skip if the incoming flag is true. + * @param string $should_skip Condition causing the tests to be skipped. + * @param string $message Text of skip condition. + * @access public + */ + function skipIf($should_skip, $message = '%s') { + if ($should_skip && ! $this->should_skip) { + $this->should_skip = true; + $message = sprintf($message, 'Skipping [' . get_class($this) . ']'); + $this->reporter->paintSkip($message . $this->getAssertionLine()); + } + } + + /** + * Accessor for the private variable $_shoud_skip + * @access public + */ + function shouldSkip() { + return $this->should_skip; + } + + /** + * Will issue a message to the reporter and tell the test + * case to skip if the incoming flag is false. + * @param string $shouldnt_skip Condition causing the tests to be run. + * @param string $message Text of skip condition. + * @access public + */ + function skipUnless($shouldnt_skip, $message = false) { + $this->skipIf(! $shouldnt_skip, $message); + } + + /** + * Used to invoke the single tests. + * @return SimpleInvoker Individual test runner. + * @access public + */ + function createInvoker() { + return new SimpleExceptionTrappingInvoker( + new SimpleErrorTrappingInvoker(new SimpleInvoker($this))); + } + + /** + * Uses reflection to run every method within itself + * starting with the string "test" unless a method + * is specified. + * @param SimpleReporter $reporter Current test reporter. + * @return boolean True if all tests passed. + * @access public + */ + function run($reporter) { + $context = SimpleTest::getContext(); + $context->setTest($this); + $context->setReporter($reporter); + $this->reporter = $reporter; + $started = false; + foreach ($this->getTests() as $method) { + if ($reporter->shouldInvoke($this->getLabel(), $method)) { + $this->skip(); + if ($this->should_skip) { + break; + } + if (! $started) { + $reporter->paintCaseStart($this->getLabel()); + $started = true; + } + $invoker = $this->reporter->createInvoker($this->createInvoker()); + $invoker->before($method); + $invoker->invoke($method); + $invoker->after($method); + } + } + if ($started) { + $reporter->paintCaseEnd($this->getLabel()); + } + unset($this->reporter); + return $reporter->getStatus(); + } + + /** + * Gets a list of test names. Normally that will + * be all internal methods that start with the + * name "test". This method should be overridden + * if you want a different rule. + * @return array List of test names. + * @access public + */ + function getTests() { + $methods = array(); + foreach (get_class_methods(get_class($this)) as $method) { + if ($this->isTest($method)) { + $methods[] = $method; + } + } + return $methods; + } + + /** + * Tests to see if the method is a test that should + * be run. Currently any method that starts with 'test' + * is a candidate unless it is the constructor. + * @param string $method Method name to try. + * @return boolean True if test method. + * @access protected + */ + protected function isTest($method) { + if (strtolower(substr($method, 0, 4)) == 'test') { + return ! SimpleTestCompatibility::isA($this, strtolower($method)); + } + return false; + } + + /** + * Announces the start of the test. + * @param string $method Test method just started. + * @access public + */ + function before($method) { + $this->reporter->paintMethodStart($method); + $this->observers = array(); + } + + /** + * Sets up unit test wide variables at the start + * of each test method. To be overridden in + * actual user test cases. + * @access public + */ + function setUp() { + } + + /** + * Clears the data set in the setUp() method call. + * To be overridden by the user in actual user test cases. + * @access public + */ + function tearDown() { + } + + /** + * Announces the end of the test. Includes private clean up. + * @param string $method Test method just finished. + * @access public + */ + function after($method) { + for ($i = 0; $i < count($this->observers); $i++) { + $this->observers[$i]->atTestEnd($method, $this); + } + $this->reporter->paintMethodEnd($method); + } + + /** + * Sets up an observer for the test end. + * @param object $observer Must have atTestEnd() + * method. + * @access public + */ + function tell($observer) { + $this->observers[] = &$observer; + } + + /** + * @deprecated + */ + function pass($message = "Pass") { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintPass( + $message . $this->getAssertionLine()); + return true; + } + + /** + * Sends a fail event with a message. + * @param string $message Message to send. + * @access public + */ + function fail($message = "Fail") { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintFail( + $message . $this->getAssertionLine()); + return false; + } + + /** + * Formats a PHP error and dispatches it to the + * reporter. + * @param integer $severity PHP error code. + * @param string $message Text of error. + * @param string $file File error occoured in. + * @param integer $line Line number of error. + * @access public + */ + function error($severity, $message, $file, $line) { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintError( + "Unexpected PHP error [$message] severity [$severity] in [$file line $line]"); + } + + /** + * Formats an exception and dispatches it to the + * reporter. + * @param Exception $exception Object thrown. + * @access public + */ + function exception($exception) { + $this->reporter->paintException($exception); + } + + /** + * For user defined expansion of the available messages. + * @param string $type Tag for sorting the signals. + * @param mixed $payload Extra user specific information. + */ + function signal($type, $payload) { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintSignal($type, $payload); + } + + /** + * Runs an expectation directly, for extending the + * tests with new expectation classes. + * @param SimpleExpectation $expectation Expectation subclass. + * @param mixed $compare Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assert($expectation, $compare, $message = '%s') { + if ($expectation->test($compare)) { + return $this->pass(sprintf( + $message, + $expectation->overlayMessage($compare, $this->reporter->getDumper()))); + } else { + return $this->fail(sprintf( + $message, + $expectation->overlayMessage($compare, $this->reporter->getDumper()))); + } + } + + /** + * Uses a stack trace to find the line of an assertion. + * @return string Line number of first assert* + * method embedded in format string. + * @access public + */ + function getAssertionLine() { + $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip')); + return $trace->traceMethod(); + } + + /** + * Sends a formatted dump of a variable to the + * test suite for those emergency debugging + * situations. + * @param mixed $variable Variable to display. + * @param string $message Message to display. + * @return mixed The original variable. + * @access public + */ + function dump($variable, $message = false) { + $dumper = $this->reporter->getDumper(); + $formatted = $dumper->dump($variable); + if ($message) { + $formatted = $message . "\n" . $formatted; + } + $this->reporter->paintFormattedMessage($formatted); + return $variable; + } + + /** + * Accessor for the number of subtests including myelf. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + return 1; + } +} + +/** + * Helps to extract test cases automatically from a file. + */ +class SimpleFileLoader { + + /** + * Builds a test suite from a library of test cases. + * The new suite is composed into this one. + * @param string $test_file File name of library with + * test case classes. + * @return TestSuite The new test suite. + * @access public + */ + function load($test_file) { + $existing_classes = get_declared_classes(); + $existing_globals = get_defined_vars(); + include_once($test_file); + $new_globals = get_defined_vars(); + $this->makeFileVariablesGlobal($existing_globals, $new_globals); + $new_classes = array_diff(get_declared_classes(), $existing_classes); + if (empty($new_classes)) { + $new_classes = $this->scrapeClassesFromFile($test_file); + } + $classes = $this->selectRunnableTests($new_classes); + return $this->createSuiteFromClasses($test_file, $classes); + } + + /** + * Imports new variables into the global namespace. + * @param hash $existing Variables before the file was loaded. + * @param hash $new Variables after the file was loaded. + * @access private + */ + protected function makeFileVariablesGlobal($existing, $new) { + $globals = array_diff(array_keys($new), array_keys($existing)); + foreach ($globals as $global) { + $_GLOBALS[$global] = $new[$global]; + } + } + + /** + * Lookup classnames from file contents, in case the + * file may have been included before. + * Note: This is probably too clever by half. Figuring this + * out after a failed test case is going to be tricky for us, + * never mind the user. A test case should not be included + * twice anyway. + * @param string $test_file File name with classes. + * @access private + */ + protected function scrapeClassesFromFile($test_file) { + preg_match_all('~^\s*class\s+(\w+)(\s+(extends|implements)\s+\w+)*\s*\{~mi', + file_get_contents($test_file), + $matches ); + return $matches[1]; + } + + /** + * Calculates the incoming test cases. Skips abstract + * and ignored classes. + * @param array $candidates Candidate classes. + * @return array New classes which are test + * cases that shouldn't be ignored. + * @access public + */ + function selectRunnableTests($candidates) { + $classes = array(); + foreach ($candidates as $class) { + if (TestSuite::getBaseTestCase($class)) { + $reflection = new SimpleReflection($class); + if ($reflection->isAbstract()) { + SimpleTest::ignore($class); + } else { + $classes[] = $class; + } + } + } + return $classes; + } + + /** + * Builds a test suite from a class list. + * @param string $title Title of new group. + * @param array $classes Test classes. + * @return TestSuite Group loaded with the new + * test cases. + * @access public + */ + function createSuiteFromClasses($title, $classes) { + if (count($classes) == 0) { + $suite = new BadTestSuite($title, "No runnable test cases in [$title]"); + return $suite; + } + SimpleTest::ignoreParentsIfIgnored($classes); + $suite = new TestSuite($title); + foreach ($classes as $class) { + if (! SimpleTest::isIgnored($class)) { + $suite->add($class); + } + } + return $suite; + } +} + +/** + * This is a composite test class for combining + * test cases and other RunnableTest classes into + * a group test. + * @package SimpleTest + * @subpackage UnitTester + */ +class TestSuite { + private $label; + private $test_cases; + + /** + * Sets the name of the test suite. + * @param string $label Name sent at the start and end + * of the test. + * @access public + */ + function TestSuite($label = false) { + $this->label = $label; + $this->test_cases = array(); + } + + /** + * Accessor for the test name for subclasses. If the suite + * wraps a single test case the label defaults to the name of that test. + * @return string Name of the test. + * @access public + */ + function getLabel() { + if (! $this->label) { + return ($this->getSize() == 1) ? + get_class($this->test_cases[0]) : get_class($this); + } else { + return $this->label; + } + } + + /** + * Adds a test into the suite by instance or class. The class will + * be instantiated if it's a test suite. + * @param SimpleTestCase $test_case Suite or individual test + * case implementing the + * runnable test interface. + * @access public + */ + function add($test_case) { + if (! is_string($test_case)) { + $this->test_cases[] = $test_case; + } elseif (TestSuite::getBaseTestCase($test_case) == 'testsuite') { + $this->test_cases[] = new $test_case(); + } else { + $this->test_cases[] = $test_case; + } + } + + /** + * Builds a test suite from a library of test cases. + * The new suite is composed into this one. + * @param string $test_file File name of library with + * test case classes. + * @access public + */ + function addFile($test_file) { + $extractor = new SimpleFileLoader(); + $this->add($extractor->load($test_file)); + } + + /** + * Delegates to a visiting collector to add test + * files. + * @param string $path Path to scan from. + * @param SimpleCollector $collector Directory scanner. + * @access public + */ + function collect($path, $collector) { + $collector->collect($this, $path); + } + + /** + * Invokes run() on all of the held test cases, instantiating + * them if necessary. + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run($reporter) { + $reporter->paintGroupStart($this->getLabel(), $this->getSize()); + for ($i = 0, $count = count($this->test_cases); $i < $count; $i++) { + if (is_string($this->test_cases[$i])) { + $class = $this->test_cases[$i]; + $test = new $class(); + $test->run($reporter); + unset($test); + } else { + $this->test_cases[$i]->run($reporter); + } + } + $reporter->paintGroupEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Number of contained test cases. + * @return integer Total count of cases in the group. + * @access public + */ + function getSize() { + $count = 0; + foreach ($this->test_cases as $case) { + if (is_string($case)) { + if (! SimpleTest::isIgnored($case)) { + $count++; + } + } else { + $count += $case->getSize(); + } + } + return $count; + } + + /** + * Test to see if a class is derived from the + * SimpleTestCase class. + * @param string $class Class name. + * @access public + */ + static function getBaseTestCase($class) { + while ($class = get_parent_class($class)) { + $class = strtolower($class); + if ($class == 'simpletestcase' || $class == 'testsuite') { + return $class; + } + } + return false; + } +} + +/** + * This is a failing group test for when a test suite hasn't + * loaded properly. + * @package SimpleTest + * @subpackage UnitTester + */ +class BadTestSuite { + private $label; + private $error; + + /** + * Sets the name of the test suite and error message. + * @param string $label Name sent at the start and end + * of the test. + * @access public + */ + function BadTestSuite($label, $error) { + $this->label = $label; + $this->error = $error; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->label; + } + + /** + * Sends a single error to the reporter. + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run($reporter) { + $reporter->paintGroupStart($this->getLabel(), $this->getSize()); + $reporter->paintFail('Bad TestSuite [' . $this->getLabel() . + '] with error [' . $this->error . ']'); + $reporter->paintGroupEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Number of contained test cases. Always zero. + * @return integer Total count of cases in the group. + * @access public + */ + function getSize() { + return 0; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/unit_tester.php b/lib/Swift/test-suite/lib/simpletest/unit_tester.php new file mode 100644 index 0000000..513f09a --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/unit_tester.php @@ -0,0 +1,402 @@ +assert(new TrueExpectation(), $result, $message); + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = '%s') { + return $this->assert(new FalseExpectation(), $result, $message); + } + + /** + * Will be true if the value is null. + * @param null $value Supposedly null value. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNull($value, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($value) . '] should be null'); + return $this->assertTrue(! isset($value), $message); + } + + /** + * Will be true if the value is set. + * @param mixed $value Supposedly set value. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertNotNull($value, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($value) . '] should not be null'); + return $this->assertTrue(isset($value), $message); + } + + /** + * Type and class test. Will pass if class + * matches the type name or is a subclass or + * if not an object, but the type is correct. + * @param mixed $object Object to test. + * @param string $type Type name as string. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertIsA($object, $type, $message = '%s') { + return $this->assert( + new IsAExpectation($type), + $object, + $message); + } + + /** + * Type and class mismatch test. Will pass if class + * name or underling type does not match the one + * specified. + * @param mixed $object Object to test. + * @param string $type Type name as string. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertNotA($object, $type, $message = '%s') { + return $this->assert( + new NotAExpectation($type), + $object, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = '%s') { + return $this->assert( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = '%s') { + return $this->assert( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the if the first parameter + * is near enough to the second by the margin. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param mixed $margin Fuzziness of match. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertWithinMargin($first, $second, $margin, $message = '%s') { + return $this->assert( + new WithinMarginExpectation($first, $margin), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters differ + * by more than the margin. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param mixed $margin Fuzziness of match. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertOutsideMargin($first, $second, $margin, $message = '%s') { + return $this->assert( + new OutsideMarginExpectation($first, $margin), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value and same type. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertIdentical($first, $second, $message = '%s') { + return $this->assert( + new IdenticalExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the different value or different type. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotIdentical($first, $second, $message = '%s') { + return $this->assert( + new NotIdenticalExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if both parameters refer + * to the same object or value. Fail otherwise. + * This will cause problems testing objects under + * E_STRICT. + * TODO: Replace with expectation. + * @param mixed $first Reference to check. + * @param mixed $second Hopefully the same variable. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertReference(&$first, &$second, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should reference the same object'); + return $this->assertTrue( + SimpleTestCompatibility::isReference($first, $second), + $message); + } + + /** + * Will trigger a pass if both parameters refer + * to the same object. Fail otherwise. This has + * the same semantics at the PHPUnit assertSame. + * That is, if values are passed in it has roughly + * the same affect as assertIdentical. + * TODO: Replace with expectation. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertSame($first, $second, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should reference the same object'); + return $this->assertTrue($first === $second, $message); + } + + /** + * Will trigger a pass if both parameters refer + * to different objects. Fail otherwise. The objects + * have to be identical though. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully not the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertClone($first, $second, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should not be the same object'); + $identical = new IdenticalExpectation($first); + return $this->assertTrue( + $identical->test($second) && ! ($first === $second), + $message); + } + + /** + * Will trigger a pass if both parameters refer + * to different variables. Fail otherwise. The objects + * have to be identical references though. + * This will fail under E_STRICT with objects. Use + * assertClone() for this. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully not the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertCopy(&$first, &$second, $message = "%s") { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($first) . + "] and [" . $dumper->describeValue($second) . + "] should not be the same object"); + return $this->assertFalse( + SimpleTestCompatibility::isReference($first, $second), + $message); + } + + /** + * Will trigger a pass if the Perl regex pattern + * is found in the subject. Fail otherwise. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $subject String to search in. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertPattern($pattern, $subject, $message = '%s') { + return $this->assert( + new PatternExpectation($pattern), + $subject, + $message); + } + + /** + * Will trigger a pass if the perl regex pattern + * is not present in subject. Fail if found. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $subject String to search in. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNoPattern($pattern, $subject, $message = '%s') { + return $this->assert( + new NoPatternExpectation($pattern), + $subject, + $message); + } + + /** + * Prepares for an error. If the error mismatches it + * passes through, otherwise it is swallowed. Any + * left over errors trigger failures. + * @param SimpleExpectation/string $expected The error to match. + * @param string $message Message on failure. + * @access public + */ + function expectError($expected = false, $message = '%s') { + $queue = SimpleTest::getContext()->get('SimpleErrorQueue'); + $queue->expectError($this->coerceExpectation($expected), $message); + } + + /** + * Prepares for an exception. If the error mismatches it + * passes through, otherwise it is swallowed. Any + * left over errors trigger failures. + * @param SimpleExpectation/Exception $expected The error to match. + * @param string $message Message on failure. + * @access public + */ + function expectException($expected = false, $message = '%s') { + $queue = SimpleTest::getContext()->get('SimpleExceptionTrap'); + $line = $this->getAssertionLine(); + $queue->expectException($expected, $message . $line); + } + + /** + * Creates an equality expectation if the + * object/value is not already some type + * of expectation. + * @param mixed $expected Expected value. + * @return SimpleExpectation Expectation object. + * @access private + */ + protected function coerceExpectation($expected) { + if ($expected == false) { + return new TrueExpectation(); + } + if (SimpleTestCompatibility::isA($expected, 'SimpleExpectation')) { + return $expected; + } + return new EqualExpectation( + is_string($expected) ? str_replace('%', '%%', $expected) : $expected); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/url.php b/lib/Swift/test-suite/lib/simpletest/url.php new file mode 100644 index 0000000..00b0bca --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/url.php @@ -0,0 +1,550 @@ +chompCoordinates($url); + $this->setCoordinates($x, $y); + $this->scheme = $this->chompScheme($url); + if ($this->scheme === 'file') { + // Unescaped backslashes not used in directory separator context + // will get caught by this, but they should have been urlencoded + // anyway so we don't care. If this ends up being a problem, the + // host regexp must be modified to match for backslashes when + // the scheme is file. + $url = str_replace('\\', '/', $url); + } + list($this->username, $this->password) = $this->chompLogin($url); + $this->host = $this->chompHost($url); + $this->port = false; + if (preg_match('/(.*?):(.*)/', $this->host, $host_parts)) { + if ($this->scheme === 'file' && strlen($this->host) === 2) { + // DOS drive was placed in authority; promote it to path. + $url = '/' . $this->host . $url; + $this->host = false; + } else { + $this->host = $host_parts[1]; + $this->port = (integer)$host_parts[2]; + } + } + $this->path = $this->chompPath($url); + $this->request = $this->parseRequest($this->chompRequest($url)); + $this->fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false); + $this->target = false; + } + + /** + * Extracts the X, Y coordinate pair from an image map. + * @param string $url URL so far. The coordinates will be + * removed. + * @return array X, Y as a pair of integers. + * @access private + */ + protected function chompCoordinates(&$url) { + if (preg_match('/(.*)\?(\d+),(\d+)$/', $url, $matches)) { + $url = $matches[1]; + return array((integer)$matches[2], (integer)$matches[3]); + } + return array(false, false); + } + + /** + * Extracts the scheme part of an incoming URL. + * @param string $url URL so far. The scheme will be + * removed. + * @return string Scheme part or false. + * @access private + */ + protected function chompScheme(&$url) { + if (preg_match('#^([^/:]*):(//)(.*)#', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return false; + } + + /** + * Extracts the username and password from the + * incoming URL. The // prefix will be reattached + * to the URL after the doublet is extracted. + * @param string $url URL so far. The username and + * password are removed. + * @return array Two item list of username and + * password. Will urldecode() them. + * @access private + */ + protected function chompLogin(&$url) { + $prefix = ''; + if (preg_match('#^(//)(.*)#', $url, $matches)) { + $prefix = $matches[1]; + $url = $matches[2]; + } + if (preg_match('#^([^/]*)@(.*)#', $url, $matches)) { + $url = $prefix . $matches[2]; + $parts = split(":", $matches[1]); + return array( + urldecode($parts[0]), + isset($parts[1]) ? urldecode($parts[1]) : false); + } + $url = $prefix . $url; + return array(false, false); + } + + /** + * Extracts the host part of an incoming URL. + * Includes the port number part. Will extract + * the host if it starts with // or it has + * a top level domain or it has at least two + * dots. + * @param string $url URL so far. The host will be + * removed. + * @return string Host part guess or false. + * @access private + */ + protected function chompHost(&$url) { + if (preg_match('!^(//)(.*?)(/.*|\?.*|#.*|$)!', $url, $matches)) { + $url = $matches[3]; + return $matches[2]; + } + if (preg_match('!(.*?)(\.\./|\./|/|\?|#|$)(.*)!', $url, $matches)) { + $tlds = SimpleUrl::getAllTopLevelDomains(); + if (preg_match('/[a-z0-9\-]+\.(' . $tlds . ')/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } elseif (preg_match('/[a-z0-9\-]+\.[a-z0-9\-]+\.[a-z0-9\-]+/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + } + return false; + } + + /** + * Extracts the path information from the incoming + * URL. Strips this path from the URL. + * @param string $url URL so far. The host will be + * removed. + * @return string Path part or '/'. + * @access private + */ + protected function chompPath(&$url) { + if (preg_match('/(.*?)(\?|#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return ($matches[1] ? $matches[1] : ''); + } + return ''; + } + + /** + * Strips off the request data. + * @param string $url URL so far. The request will be + * removed. + * @return string Raw request part. + * @access private + */ + protected function chompRequest(&$url) { + if (preg_match('/\?(.*?)(#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return ''; + } + + /** + * Breaks the request down into an object. + * @param string $raw Raw request. + * @return SimpleFormEncoding Parsed data. + * @access private + */ + protected function parseRequest($raw) { + $this->raw = $raw; + $request = new SimpleGetEncoding(); + foreach (split("&", $raw) as $pair) { + if (preg_match('/(.*?)=(.*)/', $pair, $matches)) { + $request->add($matches[1], urldecode($matches[2])); + } elseif ($pair) { + $request->add($pair, ''); + } + } + return $request; + } + + /** + * Accessor for protocol part. + * @param string $default Value to use if not present. + * @return string Scheme name, e.g "http". + * @access public + */ + function getScheme($default = false) { + return $this->scheme ? $this->scheme : $default; + } + + /** + * Accessor for user name. + * @return string Username preceding host. + * @access public + */ + function getUsername() { + return $this->username; + } + + /** + * Accessor for password. + * @return string Password preceding host. + * @access public + */ + function getPassword() { + return $this->password; + } + + /** + * Accessor for hostname and port. + * @param string $default Value to use if not present. + * @return string Hostname only. + * @access public + */ + function getHost($default = false) { + return $this->host ? $this->host : $default; + } + + /** + * Accessor for top level domain. + * @return string Last part of host. + * @access public + */ + function getTld() { + $path_parts = pathinfo($this->getHost()); + return (isset($path_parts['extension']) ? $path_parts['extension'] : false); + } + + /** + * Accessor for port number. + * @return integer TCP/IP port number. + * @access public + */ + function getPort() { + return $this->port; + } + + /** + * Accessor for path. + * @return string Full path including leading slash if implied. + * @access public + */ + function getPath() { + if (! $this->path && $this->host) { + return '/'; + } + return $this->path; + } + + /** + * Accessor for page if any. This may be a + * directory name if ambiguious. + * @return Page name. + * @access public + */ + function getPage() { + if (! preg_match('/([^\/]*?)$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Gets the path to the page. + * @return string Path less the page. + * @access public + */ + function getBasePath() { + if (! preg_match('/(.*\/)[^\/]*?$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Accessor for fragment at end of URL after the "#". + * @return string Part after "#". + * @access public + */ + function getFragment() { + return $this->fragment; + } + + /** + * Sets image coordinates. Set to false to clear + * them. + * @param integer $x Horizontal position. + * @param integer $y Vertical position. + * @access public + */ + function setCoordinates($x = false, $y = false) { + if (($x === false) || ($y === false)) { + $this->x = $this->y = false; + return; + } + $this->x = (integer)$x; + $this->y = (integer)$y; + } + + /** + * Accessor for horizontal image coordinate. + * @return integer X value. + * @access public + */ + function getX() { + return $this->x; + } + + /** + * Accessor for vertical image coordinate. + * @return integer Y value. + * @access public + */ + function getY() { + return $this->y; + } + + /** + * Accessor for current request parameters + * in URL string form. Will return teh original request + * if at all possible even if it doesn't make much + * sense. + * @return string Form is string "?a=1&b=2", etc. + * @access public + */ + function getEncodedRequest() { + if ($this->raw) { + $encoded = $this->raw; + } else { + $encoded = $this->request->asUrlRequest(); + } + if ($encoded) { + return '?' . preg_replace('/^\?/', '', $encoded); + } + return ''; + } + + /** + * Adds an additional parameter to the request. + * @param string $key Name of parameter. + * @param string $value Value as string. + * @access public + */ + function addRequestParameter($key, $value) { + $this->raw = false; + $this->request->add($key, $value); + } + + /** + * Adds additional parameters to the request. + * @param hash/SimpleFormEncoding $parameters Additional + * parameters. + * @access public + */ + function addRequestParameters($parameters) { + $this->raw = false; + $this->request->merge($parameters); + } + + /** + * Clears down all parameters. + * @access public + */ + function clearRequest() { + $this->raw = false; + $this->request = new SimpleGetEncoding(); + } + + /** + * Gets the frame target if present. Although + * not strictly part of the URL specification it + * acts as similarily to the browser. + * @return boolean/string Frame name or false if none. + * @access public + */ + function getTarget() { + return $this->target; + } + + /** + * Attaches a frame target. + * @param string $frame Name of frame. + * @access public + */ + function setTarget($frame) { + $this->raw = false; + $this->target = $frame; + } + + /** + * Renders the URL back into a string. + * @return string URL in canonical form. + * @access public + */ + function asString() { + $path = $this->path; + $scheme = $identity = $host = $port = $encoded = $fragment = ''; + if ($this->username && $this->password) { + $identity = $this->username . ':' . $this->password . '@'; + } + if ($this->getHost()) { + $scheme = $this->getScheme() ? $this->getScheme() : 'http'; + $scheme .= '://'; + $host = $this->getHost(); + } elseif ($this->getScheme() === 'file') { + // Safest way; otherwise, file URLs on Windows have an extra + // leading slash. It might be possible to convert file:// + // URIs to local file paths, but that requires more research. + $scheme = 'file://'; + } + if ($this->getPort() && $this->getPort() != 80 ) { + $port = ':'.$this->getPort(); + } + + if (substr($this->path, 0, 1) == '/') { + $path = $this->normalisePath($this->path); + } + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return "$scheme$identity$host$port$path$encoded$fragment$coords"; + } + + /** + * Replaces unknown sections to turn a relative + * URL into an absolute one. The base URL can + * be either a string or a SimpleUrl object. + * @param string/SimpleUrl $base Base URL. + * @access public + */ + function makeAbsolute($base) { + if (! is_object($base)) { + $base = new SimpleUrl($base); + } + if ($this->getHost()) { + $scheme = $this->getScheme(); + $host = $this->getHost(); + $port = $this->getPort() ? ':' . $this->getPort() : ''; + $identity = $this->getIdentity() ? $this->getIdentity() . '@' : ''; + if (! $identity) { + $identity = $base->getIdentity() ? $base->getIdentity() . '@' : ''; + } + } else { + $scheme = $base->getScheme(); + $host = $base->getHost(); + $port = $base->getPort() ? ':' . $base->getPort() : ''; + $identity = $base->getIdentity() ? $base->getIdentity() . '@' : ''; + } + $path = $this->normalisePath($this->extractAbsolutePath($base)); + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords"); + } + + /** + * Replaces unknown sections of the path with base parts + * to return a complete absolute one. + * @param string/SimpleUrl $base Base URL. + * @param string Absolute path. + * @access private + */ + protected function extractAbsolutePath($base) { + if ($this->getHost()) { + return $this->path; + } + if (! $this->isRelativePath($this->path)) { + return $this->path; + } + if ($this->path) { + return $base->getBasePath() . $this->path; + } + return $base->getPath(); + } + + /** + * Simple test to see if a path part is relative. + * @param string $path Path to test. + * @return boolean True if starts with a "/". + * @access private + */ + protected function isRelativePath($path) { + return (substr($path, 0, 1) != '/'); + } + + /** + * Extracts the username and password for use in rendering + * a URL. + * @return string/boolean Form of username:password or false. + * @access public + */ + function getIdentity() { + if ($this->username && $this->password) { + return $this->username . ':' . $this->password; + } + return false; + } + + /** + * Replaces . and .. sections of the path. + * @param string $path Unoptimised path. + * @return string Path with dots removed if possible. + * @access public + */ + function normalisePath($path) { + $path = preg_replace('|/\./|', '/', $path); + return preg_replace('|/[^/]+/\.\./|', '/', $path); + } + + /** + * A pipe seperated list of all TLDs that result in two part + * domain names. + * @return string Pipe separated list. + * @access public + */ + static function getAllTopLevelDomains() { + return 'com|edu|net|org|gov|mil|int|biz|info|name|pro|aero|coop|museum'; + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/user_agent.php b/lib/Swift/test-suite/lib/simpletest/user_agent.php new file mode 100644 index 0000000..c7f6536 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/user_agent.php @@ -0,0 +1,328 @@ +cookie_jar = new SimpleCookieJar(); + $this->authenticator = new SimpleAuthenticator(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. Authorisation + * has to be obtained again as well. + * @param string/integer $date Time when session restarted. + * If omitted then all persistent + * cookies are kept. + * @access public + */ + function restart($date = false) { + $this->cookie_jar->restartSession($date); + $this->authenticator->restartSession(); + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->additional_headers[] = $header; + } + + /** + * Ages the cookies by the specified time. + * @param integer $interval Amount in seconds. + * @access public + */ + function ageCookies($interval) { + $this->cookie_jar->agePrematurely($interval); + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $this->cookie_jar->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + return $this->cookie_jar->getCookieValue($host, $path, $name); + } + + /** + * Reads the current cookies within the base URL. + * @param string $name Key of cookie to find. + * @param SimpleUrl $base Base URL to search from. + * @return string/boolean Null if there is no base URL, false + * if the cookie is not set. + * @access public + */ + function getBaseCookieValue($name, $base) { + if (! $base) { + return null; + } + return $this->getCookieValue($base->getHost(), $base->getPath(), $name); + } + + /** + * Switches off cookie sending and recieving. + * @access public + */ + function ignoreCookies() { + $this->cookies_enabled = false; + } + + /** + * Switches back on the cookie sending and recieving. + * @access public + */ + function useCookies() { + $this->cookies_enabled = true; + } + + /** + * Sets the socket timeout for opening a connection. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->connection_timeout = $timeout; + } + + /** + * Sets the maximum number of redirects before + * a page will be loaded anyway. + * @param integer $max Most hops allowed. + * @access public + */ + function setMaximumRedirects($max) { + $this->max_redirects = $max; + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username, $password) { + if (! $proxy) { + $this->proxy = false; + return; + } + if ((strncmp($proxy, 'http://', 7) != 0) && (strncmp($proxy, 'https://', 8) != 0)) { + $proxy = 'http://'. $proxy; + } + $this->proxy = new SimpleUrl($proxy); + $this->proxy_username = $username; + $this->proxy_password = $password; + } + + /** + * Test to see if the redirect limit is passed. + * @param integer $redirects Count so far. + * @return boolean True if over. + * @access private + */ + protected function isTooManyRedirects($redirects) { + return ($redirects > $this->max_redirects); + } + + /** + * Sets the identity for the current realm. + * @param string $host Host to which realm applies. + * @param string $realm Full name of realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + function setIdentity($host, $realm, $username, $password) { + $this->authenticator->setIdentityForRealm($host, $realm, $username, $password); + } + + /** + * Fetches a URL as a response object. Will keep trying if redirected. + * It will also collect authentication realm information. + * @param string/SimpleUrl $url Target to fetch. + * @param SimpleEncoding $encoding Additional parameters for request. + * @return SimpleHttpResponse Hopefully the target page. + * @access public + */ + function fetchResponse($url, $encoding) { + if ($encoding->getMethod() != 'POST') { + $url->addRequestParameters($encoding); + $encoding->clear(); + } + $response = $this->fetchWhileRedirected($url, $encoding); + if ($headers = $response->getHeaders()) { + if ($headers->isChallenge()) { + $this->authenticator->addRealm( + $url, + $headers->getAuthentication(), + $headers->getRealm()); + } + } + return $response; + } + + /** + * Fetches the page until no longer redirected or + * until the redirect limit runs out. + * @param SimpleUrl $url Target to fetch. + * @param SimpelFormEncoding $encoding Additional parameters for request. + * @return SimpleHttpResponse Hopefully the target page. + * @access private + */ + protected function fetchWhileRedirected($url, $encoding) { + $redirects = 0; + do { + $response = $this->fetch($url, $encoding); + if ($response->isError()) { + return $response; + } + $headers = $response->getHeaders(); + $location = new SimpleUrl($headers->getLocation()); + $url = $location->makeAbsolute($url); + if ($this->cookies_enabled) { + $headers->writeCookiesToJar($this->cookie_jar, $url); + } + if (! $headers->isRedirect()) { + break; + } + $encoding = new SimpleGetEncoding(); + } while (! $this->isTooManyRedirects(++$redirects)); + return $response; + } + + /** + * Actually make the web request. + * @param SimpleUrl $url Target to fetch. + * @param SimpleFormEncoding $encoding Additional parameters for request. + * @return SimpleHttpResponse Headers and hopefully content. + * @access protected + */ + protected function fetch($url, $encoding) { + $request = $this->createRequest($url, $encoding); + return $request->fetch($this->connection_timeout); + } + + /** + * Creates a full page request. + * @param SimpleUrl $url Target to fetch as url object. + * @param SimpleFormEncoding $encoding POST/GET parameters. + * @return SimpleHttpRequest New request. + * @access private + */ + protected function createRequest($url, $encoding) { + $request = $this->createHttpRequest($url, $encoding); + $this->addAdditionalHeaders($request); + if ($this->cookies_enabled) { + $request->readCookiesFromJar($this->cookie_jar, $url); + } + $this->authenticator->addHeaders($request, $url); + return $request; + } + + /** + * Builds the appropriate HTTP request object. + * @param SimpleUrl $url Target to fetch as url object. + * @param SimpleFormEncoding $parameters POST/GET parameters. + * @return SimpleHttpRequest New request object. + * @access protected + */ + protected function createHttpRequest($url, $encoding) { + return new SimpleHttpRequest($this->createRoute($url), $encoding); + } + + /** + * Sets up either a direct route or via a proxy. + * @param SimpleUrl $url Target to fetch as url object. + * @return SimpleRoute Route to take to fetch URL. + * @access protected + */ + protected function createRoute($url) { + if ($this->proxy) { + return new SimpleProxyRoute( + $url, + $this->proxy, + $this->proxy_username, + $this->proxy_password); + } + return new SimpleRoute($url); + } + + /** + * Adds additional manual headers. + * @param SimpleHttpRequest $request Outgoing request. + * @access private + */ + protected function addAdditionalHeaders(&$request) { + foreach ($this->additional_headers as $header) { + $request->addHeaderLine($header); + } + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/web_tester.php b/lib/Swift/test-suite/lib/simpletest/web_tester.php new file mode 100644 index 0000000..9c3cb06 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/web_tester.php @@ -0,0 +1,1495 @@ +value = $value; + } + + /** + * Tests the expectation. True if it matches + * a string value or an array value in any order. + * @param mixed $compare Comparison value. False for + * an unset field. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + if ($this->value === false) { + return ($compare === false); + } + if ($this->isSingle($this->value)) { + return $this->testSingle($compare); + } + if (is_array($this->value)) { + return $this->testMultiple($compare); + } + return false; + } + + /** + * Tests for valid field comparisons with a single option. + * @param mixed $value Value to type check. + * @return boolean True if integer, string or float. + * @access private + */ + protected function isSingle($value) { + return is_string($value) || is_integer($value) || is_float($value); + } + + /** + * String comparison for simple field with a single option. + * @param mixed $compare String to test against. + * @returns boolean True if matching. + * @access private + */ + protected function testSingle($compare) { + if (is_array($compare) && count($compare) == 1) { + $compare = $compare[0]; + } + if (! $this->isSingle($compare)) { + return false; + } + return ($this->value == $compare); + } + + /** + * List comparison for multivalue field. + * @param mixed $compare List in any order to test against. + * @returns boolean True if matching. + * @access private + */ + protected function testMultiple($compare) { + if (is_string($compare)) { + $compare = array($compare); + } + if (! is_array($compare)) { + return false; + } + sort($compare); + return ($this->value === $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if (is_array($compare)) { + sort($compare); + } + if ($this->test($compare)) { + return "Field expectation [" . $dumper->describeValue($this->value) . "]"; + } else { + return "Field expectation [" . $dumper->describeValue($this->value) . + "] fails with [" . + $dumper->describeValue($compare) . "] " . + $dumper->describeDifference($this->value, $compare); + } + } +} + +/** + * Test for a specific HTTP header within a header block. + * @package SimpleTest + * @subpackage WebTester + */ +class HttpHeaderExpectation extends SimpleExpectation { + private $expected_header; + private $expected_value; + + /** + * Sets the field and value to compare against. + * @param string $header Case insenstive trimmed header name. + * @param mixed $value Optional value to compare. If not + * given then any value will match. If + * an expectation object then that will + * be used instead. + * @param string $message Optiona message override. Can use %s as + * a placeholder for the original message. + */ + function __construct($header, $value = false, $message = '%s') { + parent::__construct($message); + $this->expected_header = $this->normaliseHeader($header); + $this->expected_value = $value; + } + + /** + * Accessor for aggregated object. + * @return mixed Expectation set in constructor. + * @access protected + */ + protected function getExpectation() { + return $this->expected_value; + } + + /** + * Removes whitespace at ends and case variations. + * @param string $header Name of header. + * @param string Trimmed and lowecased header + * name. + * @access private + */ + protected function normaliseHeader($header) { + return strtolower(trim($header)); + } + + /** + * Tests the expectation. True if it matches + * a string value or an array value in any order. + * @param mixed $compare Raw header block to search. + * @return boolean True if header present. + * @access public + */ + function test($compare) { + return is_string($this->findHeader($compare)); + } + + /** + * Searches the incoming result. Will extract the matching + * line as text. + * @param mixed $compare Raw header block to search. + * @return string Matching header line. + * @access protected + */ + protected function findHeader($compare) { + $lines = split("\r\n", $compare); + foreach ($lines as $line) { + if ($this->testHeaderLine($line)) { + return $line; + } + } + return false; + } + + /** + * Compares a single header line against the expectation. + * @param string $line A single line to compare. + * @return boolean True if matched. + * @access private + */ + protected function testHeaderLine($line) { + if (count($parsed = split(':', $line, 2)) < 2) { + return false; + } + list($header, $value) = $parsed; + if ($this->normaliseHeader($header) != $this->expected_header) { + return false; + } + return $this->testHeaderValue($value, $this->expected_value); + } + + /** + * Tests the value part of the header. + * @param string $value Value to test. + * @param mixed $expected Value to test against. + * @return boolean True if matched. + * @access protected + */ + protected function testHeaderValue($value, $expected) { + if ($expected === false) { + return true; + } + if (SimpleExpectation::isExpectation($expected)) { + return $expected->test(trim($value)); + } + return (trim($value) == trim($expected)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Raw header block to search. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if (SimpleExpectation::isExpectation($this->expected_value)) { + $message = $this->expected_value->overlayMessage($compare, $this->getDumper()); + } else { + $message = $this->expected_header . + ($this->expected_value ? ': ' . $this->expected_value : ''); + } + if (is_string($line = $this->findHeader($compare))) { + return "Searching for header [$message] found [$line]"; + } else { + return "Failed to find header [$message]"; + } + } +} + +/** + * Test for a specific HTTP header within a header block that + * should not be found. + * @package SimpleTest + * @subpackage WebTester + */ +class NoHttpHeaderExpectation extends HttpHeaderExpectation { + private $expected_header; + private $expected_value; + + /** + * Sets the field and value to compare against. + * @param string $unwanted Case insenstive trimmed header name. + * @param string $message Optiona message override. Can use %s as + * a placeholder for the original message. + */ + function __construct($unwanted, $message = '%s') { + parent::__construct($unwanted, false, $message); + } + + /** + * Tests that the unwanted header is not found. + * @param mixed $compare Raw header block to search. + * @return boolean True if header present. + * @access public + */ + function test($compare) { + return ($this->findHeader($compare) === false); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Raw header block to search. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $expectation = $this->getExpectation(); + if (is_string($line = $this->findHeader($compare))) { + return "Found unwanted header [$expectation] with [$line]"; + } else { + return "Did not find unwanted header [$expectation]"; + } + } +} + +/** + * Test for a text substring. + * @package SimpleTest + * @subpackage UnitTester + */ +class TextExpectation extends SimpleExpectation { + private $substring; + + /** + * Sets the value to compare against. + * @param string $substring Text to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($substring, $message = '%s') { + parent::__construct($message); + $this->substring = $substring; + } + + /** + * Accessor for the substring. + * @return string Text to match. + * @access protected + */ + protected function getSubstring() { + return $this->substring; + } + + /** + * Tests the expectation. True if the text contains the + * substring. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (strpos($compare, $this->substring) !== false); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return $this->describeTextMatch($this->getSubstring(), $compare); + } else { + $dumper = $this->getDumper(); + return "Text [" . $this->getSubstring() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } + } + + /** + * Describes a pattern match including the string + * found and it's position. + * @param string $substring Text to search for. + * @param string $subject Subject to search. + * @access protected + */ + protected function describeTextMatch($substring, $subject) { + $position = strpos($subject, $substring); + $dumper = $this->getDumper(); + return "Text [$substring] detected at character [$position] in [" . + $dumper->describeValue($subject) . "] in region [" . + $dumper->clipString($subject, 100, $position) . "]"; + } +} + +/** + * Fail if a substring is detected within the + * comparison text. + * @package SimpleTest + * @subpackage UnitTester + */ +class NoTextExpectation extends TextExpectation { + + /** + * Sets the reject pattern + * @param string $substring Text to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($substring, $message = '%s') { + parent::__construct($substring, $message); + } + + /** + * Tests the expectation. False if the substring appears + * in the text. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param string $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + $dumper = $this->getDumper(); + return "Text [" . $this->getSubstring() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } else { + return $this->describeTextMatch($this->getSubstring(), $compare); + } + } +} + +/** + * Test case for testing of web pages. Allows + * fetching of pages, parsing of HTML and + * submitting forms. + * @package SimpleTest + * @subpackage WebTester + */ +class WebTestCase extends SimpleTestCase { + private $browser; + private $ignore_errors = false; + + /** + * Creates an empty test case. Should be subclassed + * with test methods for a functional test case. + * @param string $label Name of test case. Will use + * the class name if none specified. + * @access public + */ + function __construct($label = false) { + parent::__construct($label); + } + + /** + * Announces the start of the test. + * @param string $method Test method just started. + * @access public + */ + function before($method) { + parent::before($method); + $this->setBrowser($this->createBrowser()); + } + + /** + * Announces the end of the test. Includes private clean up. + * @param string $method Test method just finished. + * @access public + */ + function after($method) { + $this->unsetBrowser(); + parent::after($method); + } + + /** + * Gets a current browser reference for setting + * special expectations or for detailed + * examination of page fetches. + * @return SimpleBrowser Current test browser object. + * @access public + */ + function getBrowser() { + return $this->browser; + } + + /** + * Gets a current browser reference for setting + * special expectations or for detailed + * examination of page fetches. + * @param SimpleBrowser $browser New test browser object. + * @access public + */ + function setBrowser($browser) { + return $this->browser = $browser; + } + + /** + * Clears the current browser reference to help the + * PHP garbage collector. + * @access public + */ + function unsetBrowser() { + unset($this->browser); + } + + /** + * Creates a new default web browser object. + * Will be cleared at the end of the test method. + * @return TestBrowser New browser. + * @access public + */ + function createBrowser() { + return new SimpleBrowser(); + } + + /** + * Gets the last response error. + * @return string Last low level HTTP error. + * @access public + */ + function getTransportError() { + return $this->browser->getTransportError(); + } + + /** + * Accessor for the currently selected URL. + * @return string Current location or false if + * no page yet fetched. + * @access public + */ + function getUrl() { + return $this->browser->getUrl(); + } + + /** + * Dumps the current request for debugging. + * @access public + */ + function showRequest() { + $this->dump($this->browser->getRequest()); + } + + /** + * Dumps the current HTTP headers for debugging. + * @access public + */ + function showHeaders() { + $this->dump($this->browser->getHeaders()); + } + + /** + * Dumps the current HTML source for debugging. + * @access public + */ + function showSource() { + $this->dump($this->browser->getContent()); + } + + /** + * Dumps the visible text only for debugging. + * @access public + */ + function showText() { + $this->dump(wordwrap($this->browser->getContentAsText(), 80)); + } + + /** + * Simulates the closing and reopening of the browser. + * Temporary cookies will be discarded and timed + * cookies will be expired if later than the + * specified time. + * @param string/integer $date Time when session restarted. + * If ommitted then all persistent + * cookies are kept. Time is either + * Cookie format string or timestamp. + * @access public + */ + function restart($date = false) { + if ($date === false) { + $date = time(); + } + $this->browser->restart($date); + } + + /** + * Moves cookie expiry times back into the past. + * Useful for testing timeouts and expiries. + * @param integer $interval Amount to age in seconds. + * @access public + */ + function ageCookies($interval) { + $this->browser->ageCookies($interval); + } + + /** + * Disables frames support. Frames will not be fetched + * and the frameset page will be used instead. + * @access public + */ + function ignoreFrames() { + $this->browser->ignoreFrames(); + } + + /** + * Switches off cookie sending and recieving. + * @access public + */ + function ignoreCookies() { + $this->browser->ignoreCookies(); + } + + /** + * Skips errors for the next request only. You might + * want to confirm that a page is unreachable for + * example. + * @access public + */ + function ignoreErrors() { + $this->ignore_errors = true; + } + + /** + * Issues a fail if there is a transport error anywhere + * in the current frameset. Only one such error is + * reported. + * @param string/boolean $result HTML or failure. + * @return string/boolean $result Passes through result. + * @access private + */ + protected function failOnError($result) { + if (! $this->ignore_errors) { + if ($error = $this->browser->getTransportError()) { + $this->fail($error); + } + } + $this->ignore_errors = false; + return $result; + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->browser->addHeader($header); + } + + /** + * Sets the maximum number of redirects before + * the web page is loaded regardless. + * @param integer $max Maximum hops. + * @access public + */ + function setMaximumRedirects($max) { + if (! $this->browser) { + trigger_error( + 'Can only set maximum redirects in a test method, setUp() or tearDown()'); + } + $this->browser->setMaximumRedirects($max); + } + + /** + * Sets the socket timeout for opening a connection and + * receiving at least one byte of information. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->browser->setConnectionTimeout($timeout); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username = false, $password = false) { + $this->browser->useProxy($proxy, $username, $password); + } + + /** + * Fetches a page into the page buffer. If + * there is no base for the URL then the + * current base URL is used. After the fetch + * the base URL reflects the new location. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean/string Raw page on success. + * @access public + */ + function get($url, $parameters = false) { + return $this->failOnError($this->browser->get($url, $parameters)); + } + + /** + * Fetches a page by POST into the page buffer. + * If there is no base for the URL then the + * current base URL is used. After the fetch + * the base URL reflects the new location. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean/string Raw page on success. + * @access public + */ + function post($url, $parameters = false) { + return $this->failOnError($this->browser->post($url, $parameters)); + } + + /** + * Does a HTTP HEAD fetch, fetching only the page + * headers. The current base URL is unchanged by this. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean True on success. + * @access public + */ + function head($url, $parameters = false) { + return $this->failOnError($this->browser->head($url, $parameters)); + } + + /** + * Equivalent to hitting the retry button on the + * browser. Will attempt to repeat the page fetch. + * @return boolean True if fetch succeeded. + * @access public + */ + function retry() { + return $this->failOnError($this->browser->retry()); + } + + /** + * Equivalent to hitting the back button on the + * browser. + * @return boolean True if history entry and + * fetch succeeded. + * @access public + */ + function back() { + return $this->failOnError($this->browser->back()); + } + + /** + * Equivalent to hitting the forward button on the + * browser. + * @return boolean True if history entry and + * fetch succeeded. + * @access public + */ + function forward() { + return $this->failOnError($this->browser->forward()); + } + + /** + * Retries a request after setting the authentication + * for the current realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @return boolean/string HTML on successful fetch. Note + * that authentication may still have + * failed. + * @access public + */ + function authenticate($username, $password) { + return $this->failOnError( + $this->browser->authenticate($username, $password)); + } + + /** + * Gets the cookie value for the current browser context. + * @param string $name Name of cookie. + * @return string Value of cookie or false if unset. + * @access public + */ + function getCookie($name) { + return $this->browser->getCurrentCookieValue($name); + } + + /** + * Sets a cookie in the current browser. + * @param string $name Name of cookie. + * @param string $value Cookie value. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $this->browser->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. + * @return integer/string/boolean Label if any, otherwise + * the position in the frameset + * or false if none. + * @access public + */ + function getFrameFocus() { + return $this->browser->getFrameFocus(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + return $this->browser->setFrameFocusByIndex($choice); + } + + /** + * Sets the focus by name. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + return $this->browser->setFrameFocus($name); + } + + /** + * Clears the frame focus. All frames will be searched + * for content. + * @access public + */ + function clearFrameFocus() { + return $this->browser->clearFrameFocus(); + } + + /** + * Clicks a visible text item. Will first try buttons, + * then links and then images. + * @param string $label Visible text or alt text. + * @return string/boolean Raw page or false. + * @access public + */ + function click($label) { + return $this->failOnError($this->browser->click($label)); + } + + /** + * Checks for a click target. + * @param string $label Visible text or alt text. + * @return boolean True if click target. + * @access public + */ + function assertClickable($label, $message = '%s') { + return $this->assertTrue( + $this->browser->isClickable($label), + sprintf($message, "Click target [$label] should exist")); + } + + /** + * Clicks the submit button by label. The owning + * form will be submitted by this. + * @param string $label Button label. An unlabeled + * button can be triggered by 'Submit'. + * @param hash $additional Additional form values. + * @return boolean/string Page on success, else false. + * @access public + */ + function clickSubmit($label = 'Submit', $additional = false) { + return $this->failOnError( + $this->browser->clickSubmit($label, $additional)); + } + + /** + * Clicks the submit button by name attribute. The owning + * form will be submitted by this. + * @param string $name Name attribute of button. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickSubmitByName($name, $additional = false) { + return $this->failOnError( + $this->browser->clickSubmitByName($name, $additional)); + } + + /** + * Clicks the submit button by ID attribute. The owning + * form will be submitted by this. + * @param string $id ID attribute of button. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickSubmitById($id, $additional = false) { + return $this->failOnError( + $this->browser->clickSubmitById($id, $additional)); + } + + /** + * Checks for a valid button label. + * @param string $label Visible text. + * @return boolean True if click target. + * @access public + */ + function assertSubmit($label, $message = '%s') { + return $this->assertTrue( + $this->browser->isSubmit($label), + sprintf($message, "Submit button [$label] should exist")); + } + + /** + * Clicks the submit image by some kind of label. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $label Alt attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickImage($label, $x = 1, $y = 1, $additional = false) { + return $this->failOnError( + $this->browser->clickImage($label, $x, $y, $additional)); + } + + /** + * Clicks the submit image by the name. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $name Name attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickImageByName($name, $x = 1, $y = 1, $additional = false) { + return $this->failOnError( + $this->browser->clickImageByName($name, $x, $y, $additional)); + } + + /** + * Clicks the submit image by ID attribute. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param integer/string $id ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickImageById($id, $x = 1, $y = 1, $additional = false) { + return $this->failOnError( + $this->browser->clickImageById($id, $x, $y, $additional)); + } + + /** + * Checks for a valid image with atht alt text or title. + * @param string $label Visible text. + * @return boolean True if click target. + * @access public + */ + function assertImage($label, $message = '%s') { + return $this->assertTrue( + $this->browser->isImage($label), + sprintf($message, "Image with text [$label] should exist")); + } + + /** + * Submits a form by the ID. + * @param string $id Form ID. No button information + * is submitted this way. + * @return boolean/string Page on success. + * @access public + */ + function submitFormById($id) { + return $this->failOnError($this->browser->submitFormById($id)); + } + + /** + * Follows a link by name. Will click the first link + * found with this link text by default, or a later + * one if an index is given. Match is case insensitive + * with normalised space. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return boolean/string Page on success. + * @access public + */ + function clickLink($label, $index = 0) { + return $this->failOnError($this->browser->clickLink($label, $index)); + } + + /** + * Follows a link by id attribute. + * @param string $id ID attribute value. + * @return boolean/string Page on success. + * @access public + */ + function clickLinkById($id) { + return $this->failOnError($this->browser->clickLinkById($id)); + } + + /** + * Tests for the presence of a link label. Match is + * case insensitive with normalised space. + * @param string $label Text between the anchor tags. + * @param mixed $expected Expected URL or expectation object. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link present. + * @access public + */ + function assertLink($label, $expected = true, $message = '%s') { + $url = $this->browser->getLink($label); + if ($expected === true || ($expected !== true && $url === false)) { + return $this->assertTrue($url !== false, sprintf($message, "Link [$label] should exist")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new IdenticalExpectation($expected); + } + return $this->assert($expected, $url->asString(), sprintf($message, "Link [$label] should match")); + } + + /** + * Tests for the non-presence of a link label. Match is + * case insensitive with normalised space. + * @param string/integer $label Text between the anchor tags + * or ID attribute. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link missing. + * @access public + */ + function assertNoLink($label, $message = '%s') { + return $this->assertTrue( + $this->browser->getLink($label) === false, + sprintf($message, "Link [$label] should not exist")); + } + + /** + * Tests for the presence of a link id attribute. + * @param string $id Id attribute value. + * @param mixed $expected Expected URL or expectation object. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link present. + * @access public + */ + function assertLinkById($id, $expected = true, $message = '%s') { + $url = $this->browser->getLinkById($id); + if ($expected === true) { + return $this->assertTrue($url !== false, sprintf($message, "Link ID [$id] should exist")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new IdenticalExpectation($expected); + } + return $this->assert($expected, $url->asString(), sprintf($message, "Link ID [$id] should match")); + } + + /** + * Tests for the non-presence of a link label. Match is + * case insensitive with normalised space. + * @param string $id Id attribute value. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link missing. + * @access public + */ + function assertNoLinkById($id, $message = '%s') { + return $this->assertTrue( + $this->browser->getLinkById($id) === false, + sprintf($message, "Link ID [$id] should not exist")); + } + + /** + * Sets all form fields with that label, or name if there + * is no label attached. + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setField($label, $value, $position=false) { + return $this->browser->setField($label, $value, $position); + } + + /** + * Sets all form fields with that name. + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldByName($name, $value, $position=false) { + return $this->browser->setFieldByName($name, $value, $position); + } + + /** + * Sets all form fields with that id. + * @param string/integer $id Id of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldById($id, $value) { + return $this->browser->setFieldById($id, $value); + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form will always + * fail. If no value is given then only the existence + * of the field is checked. + * @param string $name Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertField($label, $expected = true, $message = '%s') { + $value = $this->browser->getField($label); + return $this->assertFieldValue($label, $value, $expected, $message); + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form element will always + * fail. If no value is given then only the existence + * of the field is checked. + * @param string $name Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertFieldByName($name, $expected = true, $message = '%s') { + $value = $this->browser->getFieldByName($name); + return $this->assertFieldValue($name, $value, $expected, $message); + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form will always + * fail. If no ID is given then only the existence + * of the field is checked. + * @param string/integer $id Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertFieldById($id, $expected = true, $message = '%s') { + $value = $this->browser->getFieldById($id); + return $this->assertFieldValue($id, $value, $expected, $message); + } + + /** + * Tests the field value against the expectation. + * @param string $identifier Name, ID or label. + * @param mixed $value Current field value. + * @param mixed $expected Expected value to match. + * @param string $message Failure message. + * @return boolean True if pass + * @access protected + */ + protected function assertFieldValue($identifier, $value, $expected, $message) { + if ($expected === true) { + return $this->assertTrue( + isset($value), + sprintf($message, "Field [$identifier] should exist")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $identifier = str_replace('%', '%%', $identifier); + $expected = new FieldExpectation( + $expected, + "Field [$identifier] should match with [%s]"); + } + return $this->assert($expected, $value, $message); + } + + /** + * Checks the response code against a list + * of possible values. + * @param array $responses Possible responses for a pass. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertResponse($responses, $message = '%s') { + $responses = (is_array($responses) ? $responses : array($responses)); + $code = $this->browser->getResponseCode(); + $message = sprintf($message, "Expecting response in [" . + implode(", ", $responses) . "] got [$code]"); + return $this->assertTrue(in_array($code, $responses), $message); + } + + /** + * Checks the mime type against a list + * of possible values. + * @param array $types Possible mime types for a pass. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertMime($types, $message = '%s') { + $types = (is_array($types) ? $types : array($types)); + $type = $this->browser->getMimeType(); + $message = sprintf($message, "Expecting mime type in [" . + implode(", ", $types) . "] got [$type]"); + return $this->assertTrue(in_array($type, $types), $message); + } + + /** + * Attempt to match the authentication type within + * the security realm we are currently matching. + * @param string $authentication Usually basic. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertAuthentication($authentication = false, $message = '%s') { + if (! $authentication) { + $message = sprintf($message, "Expected any authentication type, got [" . + $this->browser->getAuthentication() . "]"); + return $this->assertTrue( + $this->browser->getAuthentication(), + $message); + } else { + $message = sprintf($message, "Expected authentication [$authentication] got [" . + $this->browser->getAuthentication() . "]"); + return $this->assertTrue( + strtolower($this->browser->getAuthentication()) == strtolower($authentication), + $message); + } + } + + /** + * Checks that no authentication is necessary to view + * the desired page. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoAuthentication($message = '%s') { + $message = sprintf($message, "Expected no authentication type, got [" . + $this->browser->getAuthentication() . "]"); + return $this->assertFalse($this->browser->getAuthentication(), $message); + } + + /** + * Attempts to match the current security realm. + * @param string $realm Name of security realm. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertRealm($realm, $message = '%s') { + if (! SimpleExpectation::isExpectation($realm)) { + $realm = new EqualExpectation($realm); + } + return $this->assert( + $realm, + $this->browser->getRealm(), + "Expected realm -> $message"); + } + + /** + * Checks each header line for the required value. If no + * value is given then only an existence check is made. + * @param string $header Case insensitive header name. + * @param mixed $value Case sensitive trimmed string to + * match against. An expectation object + * can be used for pattern matching. + * @return boolean True if pass. + * @access public + */ + function assertHeader($header, $value = false, $message = '%s') { + return $this->assert( + new HttpHeaderExpectation($header, $value), + $this->browser->getHeaders(), + $message); + } + + /** + * Confirms that the header type has not been received. + * Only the landing page is checked. If you want to check + * redirect pages, then you should limit redirects so + * as to capture the page you want. + * @param string $header Case insensitive header name. + * @return boolean True if pass. + * @access public + */ + function assertNoHeader($header, $message = '%s') { + return $this->assert( + new NoHttpHeaderExpectation($header), + $this->browser->getHeaders(), + $message); + } + + /** + * Tests the text between the title tags. + * @param string/SimpleExpectation $title Expected title. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertTitle($title = false, $message = '%s') { + if (! SimpleExpectation::isExpectation($title)) { + $title = new EqualExpectation($title); + } + return $this->assert($title, $this->browser->getTitle(), $message); + } + + /** + * Will trigger a pass if the text is found in the plain + * text form of the page. + * @param string $text Text to look for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertText($text, $message = '%s') { + return $this->assert( + new TextExpectation($text), + $this->browser->getContentAsText(), + $message); + } + + /** + * Will trigger a pass if the text is not found in the plain + * text form of the page. + * @param string $text Text to look for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoText($text, $message = '%s') { + return $this->assert( + new NoTextExpectation($text), + $this->browser->getContentAsText(), + $message); + } + + /** + * Will trigger a pass if the Perl regex pattern + * is found in the raw content. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertPattern($pattern, $message = '%s') { + return $this->assert( + new PatternExpectation($pattern), + $this->browser->getContent(), + $message); + } + + /** + * Will trigger a pass if the perl regex pattern + * is not present in raw content. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoPattern($pattern, $message = '%s') { + return $this->assert( + new NoPatternExpectation($pattern), + $this->browser->getContent(), + $message); + } + + /** + * Checks that a cookie is set for the current page + * and optionally checks the value. + * @param string $name Name of cookie to test. + * @param string $expected Expected value as a string or + * false if any value will do. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertCookie($name, $expected = false, $message = '%s') { + $value = $this->getCookie($name); + if (! $expected) { + return $this->assertTrue( + $value, + sprintf($message, "Expecting cookie [$name]")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new EqualExpectation($expected); + } + return $this->assert($expected, $value, "Expecting cookie [$name] -> $message"); + } + + /** + * Checks that no cookie is present or that it has + * been successfully cleared. + * @param string $name Name of cookie to test. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoCookie($name, $message = '%s') { + return $this->assertTrue( + $this->getCookie($name) === null or $this->getCookie($name) === false, + sprintf($message, "Not expecting cookie [$name]")); + } + + /** + * Called from within the test methods to register + * passes and failures. + * @param boolean $result Pass on true. + * @param string $message Message to display describing + * the test state. + * @return boolean True on pass + * @access public + */ + function assertTrue($result, $message = false) { + return $this->assert(new TrueExpectation(), $result, $message); + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = '%s') { + return $this->assert(new FalseExpectation(), $result, $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = '%s') { + return $this->assert( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = '%s') { + return $this->assert( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Uses a stack trace to find the line of an assertion. + * @return string Line number of first assert* + * method embedded in format string. + * @access public + */ + function getAssertionLine() { + $trace = new SimpleStackTrace(array('assert', 'click', 'pass', 'fail')); + return $trace->traceMethod(); + } +} +?> \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/simpletest/xml.php b/lib/Swift/test-suite/lib/simpletest/xml.php new file mode 100644 index 0000000..54fb6f5 --- /dev/null +++ b/lib/Swift/test-suite/lib/simpletest/xml.php @@ -0,0 +1,647 @@ +namespace = ($namespace ? $namespace . ':' : ''); + $this->indent = $indent; + } + + /** + * Calculates the pretty printing indent level + * from the current level of nesting. + * @param integer $offset Extra indenting level. + * @return string Leading space. + * @access protected + */ + protected function getIndent($offset = 0) { + return str_repeat( + $this->indent, + count($this->getTestList()) + $offset); + } + + /** + * Converts character string to parsed XML + * entities string. + * @param string text Unparsed character data. + * @return string Parsed character data. + * @access public + */ + function toParsedXml($text) { + return str_replace( + array('&', '<', '>', '"', '\''), + array('&', '<', '>', '"', '''), + $text); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + parent::paintGroupStart($test_name, $size); + print $this->getIndent(); + print "<" . $this->namespace . "group size=\"$size\">\n"; + print $this->getIndent(1); + print "<" . $this->namespace . "name>" . + $this->toParsedXml($test_name) . + "namespace . "name>\n"; + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintGroupEnd($test_name) { + print $this->getIndent(); + print "namespace . "group>\n"; + parent::paintGroupEnd($test_name); + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + parent::paintCaseStart($test_name); + print $this->getIndent(); + print "<" . $this->namespace . "case>\n"; + print $this->getIndent(1); + print "<" . $this->namespace . "name>" . + $this->toParsedXml($test_name) . + "namespace . "name>\n"; + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintCaseEnd($test_name) { + print $this->getIndent(); + print "namespace . "case>\n"; + parent::paintCaseEnd($test_name); + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + parent::paintMethodStart($test_name); + print $this->getIndent(); + print "<" . $this->namespace . "test>\n"; + print $this->getIndent(1); + print "<" . $this->namespace . "name>" . + $this->toParsedXml($test_name) . + "namespace . "name>\n"; + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test that is ending. + * @param integer $progress Number of test cases ending. + * @access public + */ + function paintMethodEnd($test_name) { + print $this->getIndent(); + print "namespace . "test>\n"; + parent::paintMethodEnd($test_name); + } + + /** + * Paints pass as XML. + * @param string $message Message to encode. + * @access public + */ + function paintPass($message) { + parent::paintPass($message); + print $this->getIndent(1); + print "<" . $this->namespace . "pass>"; + print $this->toParsedXml($message); + print "namespace . "pass>\n"; + } + + /** + * Paints failure as XML. + * @param string $message Message to encode. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print $this->getIndent(1); + print "<" . $this->namespace . "fail>"; + print $this->toParsedXml($message); + print "namespace . "fail>\n"; + } + + /** + * Paints error as XML. + * @param string $message Message to encode. + * @access public + */ + function paintError($message) { + parent::paintError($message); + print $this->getIndent(1); + print "<" . $this->namespace . "exception>"; + print $this->toParsedXml($message); + print "namespace . "exception>\n"; + } + + /** + * Paints exception as XML. + * @param Exception $exception Exception to encode. + * @access public + */ + function paintException($exception) { + parent::paintException($exception); + print $this->getIndent(1); + print "<" . $this->namespace . "exception>"; + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print $this->toParsedXml($message); + print "namespace . "exception>\n"; + } + + /** + * Paints the skipping message and tag. + * @param string $message Text to display in skip tag. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print $this->getIndent(1); + print "<" . $this->namespace . "skip>"; + print $this->toParsedXml($message); + print "namespace . "skip>\n"; + } + + /** + * Paints a simple supplementary message. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + parent::paintMessage($message); + print $this->getIndent(1); + print "<" . $this->namespace . "message>"; + print $this->toParsedXml($message); + print "namespace . "message>\n"; + } + + /** + * Paints a formatted ASCII message such as a + * privateiable dump. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + parent::paintFormattedMessage($message); + print $this->getIndent(1); + print "<" . $this->namespace . "formatted>"; + print ""; + print "namespace . "formatted>\n"; + } + + /** + * Serialises the event object. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @access public + */ + function paintSignal($type, $payload) { + parent::paintSignal($type, $payload); + print $this->getIndent(1); + print "<" . $this->namespace . "signal type=\"$type\">"; + print ""; + print "namespace . "signal>\n"; + } + + /** + * Paints the test document header. + * @param string $test_name First test top level + * to start. + * @access public + * @abstract + */ + function paintHeader($test_name) { + if (! SimpleReporter::inCli()) { + header('Content-type: text/xml'); + } + print "namespace) { + print " xmlns:" . $this->namespace . + "=\"www.lastcraft.com/SimpleTest/Beta3/Report\""; + } + print "?>\n"; + print "<" . $this->namespace . "run>\n"; + } + + /** + * Paints the test document footer. + * @param string $test_name The top level test. + * @access public + * @abstract + */ + function paintFooter($test_name) { + print "namespace . "run>\n"; + } +} + +/** + * Accumulator for incoming tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingXmlTag { + private $name; + private $attributes; + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingXmlTag($attributes) { + $this->name = false; + $this->attributes = $attributes; + } + + /** + * Sets the test case/method name. + * @param string $name Name of test. + * @access public + */ + function setName($name) { + $this->name = $name; + } + + /** + * Accessor for name. + * @return string Name of test. + * @access public + */ + function getName() { + return $this->name; + } + + /** + * Accessor for attributes. + * @return hash All attributes. + * @access protected + */ + protected function getAttributes() { + return $this->attributes; + } +} + +/** + * Accumulator for incoming method tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingMethodTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingMethodTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintMethodStart($this->getName()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintMethodEnd($this->getName()); + } +} + +/** + * Accumulator for incoming case tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingCaseTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingCaseTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintCaseStart($this->getName()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintCaseEnd($this->getName()); + } +} + +/** + * Accumulator for incoming group tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingGroupTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingGroupTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintGroupStart($this->getName(), $this->getSize()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintGroupEnd($this->getName()); + } + + /** + * The size in the attributes. + * @return integer Value of size attribute or zero. + * @access public + */ + function getSize() { + $attributes = $this->getAttributes(); + if (isset($attributes['SIZE'])) { + return (integer)$attributes['SIZE']; + } + return 0; + } +} + +/** + * Parser for importing the output of the XmlReporter. + * Dispatches that output to another reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleTestXmlParser { + private $listener; + private $expat; + private $tag_stack; + private $in_content_tag; + private $content; + private $attributes; + + /** + * Loads a listener with the SimpleReporter + * interface. + * @param SimpleReporter $listener Listener of tag events. + * @access public + */ + function SimpleTestXmlParser(&$listener) { + $this->listener = &$listener; + $this->expat = &$this->createParser(); + $this->tag_stack = array(); + $this->in_content_tag = false; + $this->content = ''; + $this->attributes = array(); + } + + /** + * Parses a block of XML sending the results to + * the listener. + * @param string $chunk Block of text to read. + * @return boolean True if valid XML. + * @access public + */ + function parse($chunk) { + if (! xml_parse($this->expat, $chunk)) { + trigger_error('XML parse error with ' . + xml_error_string(xml_get_error_code($this->expat))); + return false; + } + return true; + } + + /** + * Sets up expat as the XML parser. + * @return resource Expat handle. + * @access protected + */ + protected function &createParser() { + $expat = xml_parser_create(); + xml_set_object($expat, $this); + xml_set_element_handler($expat, 'startElement', 'endElement'); + xml_set_character_data_handler($expat, 'addContent'); + xml_set_default_handler($expat, 'defaultContent'); + return $expat; + } + + /** + * Opens a new test nesting level. + * @return NestedXmlTag The group, case or method tag + * to start. + * @access private + */ + protected function pushNestingTag($nested) { + array_unshift($this->tag_stack, $nested); + } + + /** + * Accessor for current test structure tag. + * @return NestedXmlTag The group, case or method tag + * being parsed. + * @access private + */ + protected function &getCurrentNestingTag() { + return $this->tag_stack[0]; + } + + /** + * Ends a nesting tag. + * @return NestedXmlTag The group, case or method tag + * just finished. + * @access private + */ + protected function popNestingTag() { + return array_shift($this->tag_stack); + } + + /** + * Test if tag is a leaf node with only text content. + * @param string $tag XML tag name. + * @return @boolean True if leaf, false if nesting. + * @private + */ + protected function isLeaf($tag) { + return in_array($tag, array( + 'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'SKIP', 'MESSAGE', 'FORMATTED', 'SIGNAL')); + } + + /** + * Handler for start of event element. + * @param resource $expat Parser handle. + * @param string $tag Element name. + * @param hash $attributes Name value pairs. + * Attributes without content + * are marked as true. + * @access protected + */ + protected function startElement($expat, $tag, $attributes) { + $this->attributes = $attributes; + if ($tag == 'GROUP') { + $this->pushNestingTag(new NestingGroupTag($attributes)); + } elseif ($tag == 'CASE') { + $this->pushNestingTag(new NestingCaseTag($attributes)); + } elseif ($tag == 'TEST') { + $this->pushNestingTag(new NestingMethodTag($attributes)); + } elseif ($this->isLeaf($tag)) { + $this->in_content_tag = true; + $this->content = ''; + } + } + + /** + * End of element event. + * @param resource $expat Parser handle. + * @param string $tag Element name. + * @access protected + */ + protected function endElement($expat, $tag) { + $this->in_content_tag = false; + if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) { + $nesting_tag = $this->popNestingTag(); + $nesting_tag->paintEnd($this->listener); + } elseif ($tag == 'NAME') { + $nesting_tag = &$this->getCurrentNestingTag(); + $nesting_tag->setName($this->content); + $nesting_tag->paintStart($this->listener); + } elseif ($tag == 'PASS') { + $this->listener->paintPass($this->content); + } elseif ($tag == 'FAIL') { + $this->listener->paintFail($this->content); + } elseif ($tag == 'EXCEPTION') { + $this->listener->paintError($this->content); + } elseif ($tag == 'SKIP') { + $this->listener->paintSkip($this->content); + } elseif ($tag == 'SIGNAL') { + $this->listener->paintSignal( + $this->attributes['TYPE'], + unserialize($this->content)); + } elseif ($tag == 'MESSAGE') { + $this->listener->paintMessage($this->content); + } elseif ($tag == 'FORMATTED') { + $this->listener->paintFormattedMessage($this->content); + } + } + + /** + * Content between start and end elements. + * @param resource $expat Parser handle. + * @param string $text Usually output messages. + * @access protected + */ + protected function addContent($expat, $text) { + if ($this->in_content_tag) { + $this->content .= $text; + } + return true; + } + + /** + * XML and Doctype handler. Discards all such content. + * @param resource $expat Parser handle. + * @param string $default Text of default content. + * @access protected + */ + protected function defaultContent($expat, $default) { + } +} +?> diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay.php new file mode 100644 index 0000000..b167915 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay.php @@ -0,0 +1,261 @@ +. + + */ + +//require 'Yay/Expectations.php'; +//require 'Yay/Matchers/OptionalMatcher.php'; +//require 'Yay/Matchers/AnyMatcher.php'; +//require 'Yay/Matchers/IdenticalMatcher.php'; +//require 'Yay/Matchers/EqualMatcher.php'; +//require 'Yay/Matchers/PatternMatcher.php'; +//require 'Yay/Matchers/ReferenceMatcher.php'; +//require 'Yay/Matchers/BoundsMatcher.php'; +//require 'Yay/Actions/ReturnValueAction.php'; +//require 'Yay/Actions/ReturnReferenceAction.php'; +//require 'Yay/Actions/ThrowAction.php'; +//require 'Yay/Actions/CallbackAction.php'; + +/** + * A convenience factory class. + * @author Chris Corbyn + * @package Yay + */ +class Yay +{ + + /** + * The classpath used for autoloading. + * @var string + * @access private + */ + private static $CLASSPATH = '.'; + + // -- Expectations + + /** + * Create a new Expectations builder instance. + * @return Yay_Expectations + */ + public static function expectations() + { + return new Yay_Expectations(); + } + + // -- Matchers + + /** + * Create a new Optional matcher, optionally wrapping $value. + * @param string $value, optional + * @return Yay_Matchers_OptionalMatcher + */ + public static function optional($value = null) + { + return new Yay_Matchers_OptionalMatcher($value); + } + + /** + * Create a new Any matcher, optionally constrained to $type. + * @param string $type, optional + * @return Yay_Matchers_AnyMatcher + */ + public static function any($type = null) + { + return new Yay_Matchers_AnyMatcher($type, true); + } + + /** + * Create a negated Any matcher, optionally constrained to $type. + * @param string $type, optional + * @return Yay_Matchers_AnyMatcher + */ + public static function none($type = null) + { + return new Yay_Matchers_AnyMatcher($type, false); + } + + /** + * Create a new Identical matcher for $value. + * @param mixed $value + * @return Yay_Matchers_IdenticalMatcher + */ + public static function identical($value) + { + return new Yay_Matchers_IdenticalMatcher($value, true); + } + + /** + * Create a negated Identical matcher for $value. + * @param mixed $value + * @return Yay_Matchers_IdenticalMatcher + */ + public static function notIdentical($value) + { + return new Yay_Matchers_IdenticalMatcher($value, false); + } + + /** + * Create a new Equal matcher for $value. + * @param mixed $value + * @return Yay_Matchers_EqualMatcher + */ + public static function equal($value) + { + return new Yay_Matchers_EqualMatcher($value, true); + } + + /** + * Create a negated Equal matcher for $value. + * @param mixed $value + * @return Yay_Matchers_EqualMatcher + */ + public static function notEqual($value) + { + return new Yay_Matchers_EqualMatcher($value, false); + } + + /** + * Create a new Pattern matcher for $pattern. + * @param string $pattern + * @return Yay_Matchers_IsAMatcher + */ + public static function pattern($pattern) + { + return new Yay_Matchers_PatternMatcher($pattern, true); + } + + /** + * Create a negated Pattern matcher for $pattern. + * @param string $pattern + * @return Yay_Matchers_IsAMatcher + */ + public static function noPattern($pattern) + { + return new Yay_Matchers_PatternMatcher($pattern, false); + } + + /** + * Create a new Reference matcher for $ref. + * @param mixed $ref + * @return Yay_Matchers_ReferenceMatcher + */ + public static function reference(&$ref) + { + return new Yay_Matchers_ReferenceMatcher($ref, true); + } + + /** + * Create a negated Reference matcher for $ref. + * @param mixed $ref + * @return Yay_Matchers_ReferenceMatcher + */ + public static function noReference(&$ref) + { + return new Yay_Matchers_ReferenceMatcher($ref, false); + } + + /** + * Create a new Bounds matcher for boundaries between $lower and $upper. + * @param mixed $lower + * @param mixed $upper + * @return Yay_Matchers_BoundsMatcher + */ + public static function bounds($lower, $upper) + { + return new Yay_Matchers_BoundsMatcher($lower, $upper, true); + } + + /** + * Create a negated Bounds matcher for boundaries outside $lower and $upper. + * @param mixed $lower + * @param mixed $upper + * @return Yay_Matchers_BoundsMatcher + */ + public static function outside($lower, $upper) + { + return new Yay_Matchers_BoundsMatcher($lower, $upper, false); + } + + // -- Actions + + /** + * Create a new ReturnValueAction with $value. + * @param mixed $value + * @return Yay_Actions_ReturnValueAction + */ + public static function returnValue($value) + { + return new Yay_Actions_ReturnValueAction($value); + } + + /** + * Create a new ReturnReferenceAction with &$ref. + * @param mixed $ref + * @return Yay_Actions_ReturnReferenceAction + */ + public static function returnReference(&$ref) + { + return new Yay_Actions_ReturnReferenceAction($ref); + } + + /** + * Create a new ThrowAction with $e. + * @param Exception $ref + * @return Yay_Actions_ThrowAction + */ + public static function throwException(Exception $e) + { + return new Yay_Actions_ThrowAction($e); + } + + /** + * Create a new CallbackAction with $callback. + * @param callback $callback + * @return Yay_Actions_CallbackAction + */ + public static function call($callback) + { + return new Yay_Actions_CallbackAction($callback); + } + + /** + * Set the classpath for autoloading. + * @param string $path + */ + public static function setClassPath($path) + { + self::$CLASSPATH = $path; + } + + /** + * Static autoloader registered in bootstrap file. + * @param string $class + */ + public static function autoload($class) + { + if (substr($class, 0, 3) != 'Yay') + { + return; + } + $file = str_replace('_', '/', $class) . '.php'; + $path = self::$CLASSPATH . '/' . $file; + if (is_file($path)) + { + require_once $path; + } + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Action.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Action.php new file mode 100644 index 0000000..d7b3a01 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Action.php @@ -0,0 +1,37 @@ +. + + */ + +//require 'Yay/Invocation.php'; +//require 'Yay/SelfDescribing.php'; + +/** + * An Action performed when an expected Invocation occurs. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Action extends Yay_SelfDescribing +{ + + /** + * Mimmick the method Invocation and return a value. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/CallbackAction.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/CallbackAction.php new file mode 100644 index 0000000..8611441 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/CallbackAction.php @@ -0,0 +1,66 @@ +. + + */ + +//require 'Yay/Action.php'; +//require 'Yay/Description.php'; + +/** + * An Action which delegates to a callback. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_CallbackAction implements Yay_Action +{ + + /** + * The callback to invoke. + * @var callback + * @access private + */ + private $_callback; + + /** + * Create a new CallbackAction for $callback. + * @param callback $callback + */ + public function __construct($callback) + { + $this->_callback = $callback; + } + + /** + * Mimmick the method Invocation and return a value. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation) + { + $ret = call_user_func($this->_callback, $invocation); + return $ret; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(' Runs a callback;'); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnReferenceAction.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnReferenceAction.php new file mode 100644 index 0000000..e96394d --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnReferenceAction.php @@ -0,0 +1,64 @@ +. + + */ + +//require 'Yay/Action.php'; + +/** + * An Action which returns a reference. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_ReturnReferenceAction implements Yay_Action +{ + + /** + * The reference to return. + * @var mixed + * @access private + */ + private $_ref; + + /** + * Create a new ReturnReferenceAction for &$ref. + * @param mixed $ref + */ + public function __construct(&$ref) + { + $this->_ref =& $ref; + } + + /** + * Mimmick the method Invocation and return the reference. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation) + { + return $this->_ref; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(' Returns a reference;'); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnValueAction.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnValueAction.php new file mode 100644 index 0000000..c749a1d --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ReturnValueAction.php @@ -0,0 +1,100 @@ +. + + */ + +//require 'Yay/Action.php'; + +/** + * An Action which returns a specified value. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_ReturnValueAction implements Yay_Action +{ + + /** + * The value to return. + * @var mixed + * @access private + */ + private $_value; + + /** + * Create a new ReturnValueAction for $value. + * @param mixed $value + */ + public function __construct($value) + { + $this->_value = $value; + } + + /** + * Mimmick the method Invocation and return a value. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation) + { + $value = $this->_value; + return $value; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' Returns %s;', $this->_describeValue('%s [%s]'))); + } + + private function _describeValue($format) + { + $description = ''; + $value = $this->_value; + if (is_int($value)) + { + $description = sprintf($format, 'int', $value); + } + elseif (is_float($value)) + { + $description = sprintf($format, 'float', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_numeric($value)) + { + $description = sprintf($format, 'number', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_string($value)) + { + $description = sprintf($format, 'string', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_object($value)) + { + $description = sprintf($format, 'object', get_class($value)); + } + elseif (is_array($value)) + { + $description = sprintf($format, 'array', count($value) . ' items'); + } + else + { + $description = sprintf($format, gettype($value), preg_replace('/^(.{8}).+/', '$1..', (string) $value)); + } + return $description; + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ThrowAction.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ThrowAction.php new file mode 100644 index 0000000..bdec7c8 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Actions/ThrowAction.php @@ -0,0 +1,66 @@ +. + + */ + +//require 'Yay/Action.php'; + +/** + * An Action which throws an Exception. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_ThrowAction implements Yay_Action +{ + + /** + * The Exception to throw. + * @var Exception + * @access private + */ + private $_e; + + /** + * Create a new ThrowAction for $e. + * @param Exception $e + */ + public function __construct(Exception $e) + { + $this->_e = $e; + } + + /** + * Mimmick the method Invocation and throw an Exception. + * @param Yay_Invocation $invocation + * @throws Exception + */ + public function &invoke(Yay_Invocation $invocation) + { + throw $this->_e; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText( + sprintf(' Throws %s;', get_class($this->_e)) + ); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Description.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Description.php new file mode 100644 index 0000000..561e2a2 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Description.php @@ -0,0 +1,45 @@ +. + + */ + +/** + * A Description container for error messages. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Description +{ + + /** + * Append an existing Description to this Description. + * @param Yay_Description + */ + public function appendDescription(Yay_Description $description); + + /** + * Append text content to this Description. + * @param string $text + */ + public function appendText($text); + + /** + * Get this description back as a formatted string. + * @return string + */ + public function toString(); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectation.php new file mode 100644 index 0000000..de155f9 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectation.php @@ -0,0 +1,88 @@ +. + + */ + +//require 'Yay/MockObject.php'; +//require 'Yay/Invocation.php'; +//require 'Yay/Action.php'; +//require 'Yay/SelfDescribing.php'; +//require 'Yay/State.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/Sequence.php'; + +/** + * An Invocation expectation. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Expectation extends Yay_SelfDescribing +{ + + /** + * Specify the MockObject which the Invocation will occur. + * This method should return the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_MockObject + */ + public function of(Yay_MockObject $mock); + + /** + * Notify the Expectation of an Invocation and check if it matches. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation); + + /** + * Specify the Action to run if a match occurs. + * @param Yay_Action $action + */ + public function will(Yay_Action $action); + + /** + * Only be expected when in the given State predicate. + * @param Yay_StatePredicate $predicate + */ + public function when(Yay_StatePredicate $predicate); + + /** + * Activate the given $state if a match occurs. + * @param Yay_State $state + */ + public function then(Yay_State $state); + + /** + * Constrain this expectation to be valid only if invoked in the given sequence. + * @param Yay_Sequence $sequence + */ + public function inSequence(Yay_Sequence $sequence); + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied(); + + /** + * Get the Action for the given Invocation. + * This may have been specified by a will() clause. + * @param Yay_Invocation $invocation + * @return Yay_Action + */ + public function getAction(Yay_Invocation $invocation); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/ExpectationProvider.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/ExpectationProvider.php new file mode 100644 index 0000000..8b90202 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/ExpectationProvider.php @@ -0,0 +1,33 @@ +. + + */ + +/** + * An Invocation expectation provider. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_ExpectationProvider +{ + + /** + * Returns the Expectations. + * @return array of Yay_Expectation + */ + public function getExpectations(); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations.php new file mode 100644 index 0000000..9759a7a --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations.php @@ -0,0 +1,306 @@ +. + + */ + +//require 'Yay/Expectation.php'; +//require 'Yay/InvocationRecorder.php'; +//require 'Yay/InvocationProxy.php'; +//require 'Yay/Invocation.php'; +//require 'Yay/State.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/Sequence.php'; +//require 'Yay/Expectations/ExactlyExpectation.php'; +//require 'Yay/Expectations/AtLeastExpectation.php'; +//require 'Yay/Expectations/AtMostExpectation.php'; +//require 'Yay/Expectations/BetweenExpectation.php'; +//require 'Yay/Action.php'; +//require 'Yay/Actions/ReturnValueAction.php'; +//require 'Yay/Actions/ReturnReferenceAction.php'; +//require 'Yay/Actions/ThrowAction.php'; +//require 'Yay/Actions/CallbackAction.php'; + +/** + * A group of expectations which can be specified in a fluid manner. + * Generally speaking this is where all expectations should be made for the sake + * of abstraction. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations implements Yay_InvocationRecorder +{ + + /** + * The Expectation stack. + * @var array + * @access private + */ + private $_expectations = array(); + + /** + * The current Expectation to proxy any recording to. + * @var Yay_Expectation + * @access private + */ + private $_currentEndpoint; + + /** + * Create a new instance of Expectations. + * @return Yay_Expectations + */ + final public static function create() + { + return new self(); + } + + /** + * Expect one Invocation on the $mock object. + * Returns the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function one(Yay_MockObject $mock) + { + return $this->exactly(1)->of($mock); + } + + /** + * Expect exactly $n Invocations on a mock object specified with a following + * of() clause. + * Example: Expectations::create()->exactly(2)->of($mock); + * @param int $n + * @return Yay_Expectations + */ + public function exactly($n) + { + return $this->_setEndpoint(new Yay_Expectations_ExactlyExpectation($n)); + } + + /** + * Expect at least $n Invocations on a mock object specified with a following + * of() clause. + * Example: Expectations::create()->atLeast(2)->of($mock); + * @param int $n + * @return Yay_Expectations + */ + public function atLeast($n) + { + return $this->_setEndpoint(new Yay_Expectations_AtLeastExpectation($n)); + } + + /** + * Expect at most $n Invocations on a mock object specified with a following + * of() clause. + * Example: Expectations::create()->atMost(2)->of($mock); + * @param int $n + * @return Yay_Expectations + */ + public function atMost($n) + { + return $this->_setEndpoint(new Yay_Expectations_AtMostExpectation($n)); + } + + /** + * Expect at between $min and $max Invocations on a mock object specified + * with a following of() clause. + * Example: Expectations::create()->atLeast(2)->of($mock); + * @param int $n + * @return Yay_Expectations + */ + public function between($min, $max) + { + return $this->_setEndpoint(new Yay_Expectations_BetweenExpectation($min, $max)); + } + + /** + * Ignore Invocations on the $mock object specified. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function ignoring(Yay_MockObject $mock) + { + return $this->atLeast(0)->of($mock); + } + + /** + * Allow Invocations on the $mock object specified. + * This does exactly the same thing as ignoring() but it allows a semantically + * different meaning in the test case. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function allowing(Yay_MockObject $mock) + { + return $this->ignoring($mock); + } + + /** + * Deny Invocations on the $mock object specified. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function never(Yay_MockObject $mock) + { + return $this->exactly(0)->of($mock); + } + + /** + * Specify the MockObject which the Invocation will occur. + * This method returns the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_InvocationProxy + */ + public function of(Yay_MockObject $mock) + { + $this->_getEndpoint()->of($mock); + return new Yay_InvocationProxy($this, $mock); + } + + /** + * Specify the Action to run if a match occurs. + * @param Yay_Action $action + */ + public function will(Yay_Action $action) + { + $this->_getEndpoint()->will($action); + return $this; + } + + /** + * Only be expected when in the given State predicate. + * @param Yay_StatePredicate $predicate + */ + public function when(Yay_StatePredicate $predicate) + { + $this->_getEndpoint()->when($predicate); + return $this; + } + + /** + * Activate the given $state if a match occurs. + * @param Yay_State $state + */ + public function then(Yay_State $state) + { + $this->_getEndpoint()->then($state); + return $this; + } + + /** + * Constrain the current expectation to occur in the given sequence. + * @param Yay_Sequence $seq + */ + public function inSequence(Yay_Sequence $seq) + { + $this->_getEndpoint()->inSequence($seq); + return $this; + } + + /** + * A wrapper for will(Yay::returnValue($value)). + * @param mixed $value + */ + public function returns($value) + { + $this->_getEndpoint()->will(new Yay_Actions_ReturnValueAction($value)); + return $this; + } + + /** + * A wrapper for will(Yay::returnReference($ref)). + * @param mixed $ref + */ + public function returnsReference(&$ref) + { + $this->_getEndpoint()->will(new Yay_Actions_ReturnReferenceAction($ref)); + return $this; + } + + /** + * A wrapper for will(Yay::throwException($e)). + * @param Exception $e + */ + public function throws(Exception $e) + { + $this->_getEndpoint()->will(new Yay_Actions_ThrowAction($e)); + return $this; + } + + /** + * A wrapper for will(Yay::call($callback)). + * @param callback $callback + */ + public function calls($callback) + { + $this->_getEndpoint()->will(new Yay_Actions_CallbackAction($callback)); + return $this; + } + + /** + * Record any Invocations on the MockObject whilst it's in record mode. + * @param Yay_Invocation $invocation + */ + public function recordInvocation(Yay_Invocation $invocation) + { + $this->_getEndpoint()->recordInvocation($invocation); + } + + /** + * Returns the Expectation stack. + * @return Yay_Expectation + */ + public function getExpectations() + { + return $this->_expectations; + } + + // -- Private methods + + /** + * Apply a new Expectation to the stack and tag it as the endpoint for recording. + * @param Yay_Expectation $expectation + * @return Yay_Expectations + * @access private + */ + private function _setEndpoint(Yay_Expectation $expectation) + { + $this->_expectations[] = $expectation; + $this->_currentEndpoint = $expectation; + return $this; + } + + /** + * Gets the current endpoint (current expectation). + * @return Yay_Expectation + * @access private + */ + private function _getEndpoint() + { + if (!isset($this->_currentEndpoint)) + { + throw new BadMethodCallException( + 'No cardinality clause has yet been made. First call one(), atLeast(), ' . + 'atMost(), exactly(), between(), ignoring(), allowing() or never() ' . + 'before performing this operation.' + ); + } + else + { + return $this->_currentEndpoint; + } + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AbstractExpectation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AbstractExpectation.php new file mode 100644 index 0000000..b32a29a --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AbstractExpectation.php @@ -0,0 +1,345 @@ +. + + */ + +//require 'Yay/Mockery.php'; +//require 'Yay/Expectation.php'; +//require 'Yay/ExpectationProvider.php'; +//require 'Yay/Invocation.php'; +//require 'Yay/InvocationRecorder.php'; +//require 'Yay/InvocationProxy.php'; +//require 'Yay/Action.php'; +//require 'Yay/Matcher.php'; +//require 'Yay/Matchers/IdenticalMatcher.php'; +//require 'Yay/State.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/Sequence.php'; +//require 'Yay/Description.php'; +//require 'Yay/MockGenerator.php'; + +/** + * A base Expectation which other Expectations extend. + * @author Chris Corbyn + * @package Yay + */ +abstract class Yay_Expectations_AbstractExpectation + implements Yay_Expectation, Yay_InvocationRecorder +{ + + /** + * The object to expect Invocations from. + * @var Yay_MockObject + * @access private + */ + private $_object; + + /** + * The method name to expect Invocations on. + * @var string + * @access private + */ + private $_method; + + /** + * The argument Matchers. + * @var array + * @access private + */ + private $_matchers = array(); + + /** + * The Action to use if matched. + * @var Yay_Action + * @access private + */ + private $_action; + + /** + * A state predicate to check for. + * @var Yay_StatePredicate + * @access private + */ + private $_statePredicate; + + /** + * A state to effect if matched. + * @var Yay_State + * @access private + */ + private $_state; + + /** + * The ID wanted to be valid in the Sequence. + * @var int + * @access private + */ + private $_wantedSequenceId; + + /** + * The Sequence to check for validity (if any). + * @var Yay_Sequence + * @access private + */ + private $_sequence; + + /** + * Invoked when the expectation matches so any counters can be incremented + * for example. + * @param Yay_Invocation $invocation + */ + abstract public function notifyMatchedInvocation(Yay_Invocation $invocation); + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + abstract public function describeBounds(Yay_Description $description); + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + abstract public function describeSatisfaction(Yay_Description $description); + + /** + * Specify the MockObject which the Invocation will occur. + * This method returns the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_InvocationProxy + */ + public function of(Yay_MockObject $mock) + { + $this->_object = $mock; + return new Yay_InvocationProxy($this, $mock); + } + + /** + * Notify the Expectation of an Invocation and check if it matches. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + $matches = true; + $object = $invocation->getObject(); + if ($object === $this->_object) + { + if (isset($this->_statePredicate)) + { + $matches = $this->_statePredicate->isActive(); + } + + if ($matches && isset($this->_method)) + { + if ($this->_method == $invocation->getMethod()) + { + $args =& $invocation->getArguments(); + foreach ($this->_matchers as $i => $m) + { + if (!array_key_exists($i, $args)) + { + if ($m->isOptional()) + { + break; + } + else + { + $matches = false; + break; + } + } + else + { + if (!$m->matches($args[$i])) + { + $matches = false; + break; + } + } + } + } + else + { + $matches = false; + } + } + + if ($matches && isset($this->_sequence)) + { + $matches = $this->_sequence->isInSequence($this->_wantedSequenceId); + } + } + else + { + $matches = false; + } + + if ($matches) + { + $this->notifyMatchedInvocation($invocation); + } + + return $matches; + } + + /** + * Specify the Action to run if a match occurs. + * @param Yay_Action $action + */ + public function will(Yay_Action $action) + { + $this->_action = $action; + return $this; + } + + /** + * Only be expected when in the given State predicate. + * @param Yay_StatePredicate $predicate + */ + public function when(Yay_StatePredicate $predicate) + { + $this->_statePredicate = $predicate; + return $this; + } + + /** + * Activate the given $state if a match occurs. + * @param Yay_State $state + */ + public function then(Yay_State $state) + { + $this->_state = $state; + return $this; + } + + /** + * Constrain this expectation to be valid only if invoked in the given sequence. + * @param Yay_Sequence $sequence + */ + public function inSequence(Yay_Sequence $sequence) + { + $this->_wantedSequenceId = $sequence->requestSequenceId(); + $this->_sequence = $sequence; + return $this; + } + + /** + * Get the Action for the given Invocation. + * This may have been specified by a will() clause. + * @param Yay_Invocation $invocation + * @return Yay_Action + */ + public function getAction(Yay_Invocation $invocation) + { + if (isset($this->_state)) + { + $this->_state->activate(); + } + return $this->_action; + } + + /** + * Record any Invocations on the MockObject whilst it's in record mode. + * @param Yay_Invocation $invocation + */ + public function recordInvocation(Yay_Invocation $invocation) + { + $this->_method = $invocation->getMethod(); + $matchers =& $invocation->getArguments(); + foreach ($matchers as $matcher) + { + if ($matcher instanceof Yay_Matcher) + { + $this->_matchers[] = $matcher; + } + else + { + $this->_matchers[] = new Yay_Matchers_IdenticalMatcher($matcher); + } + } + } + + /** + * Returns the Expectations. + * @return array of Yay_Expectation + */ + public function getExpectations() + { + return array($this); + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $this->describeBounds($description); + + $description->appendText(sprintf(' of %s;', $this->_getInvocationSignature())); + + if (isset($this->_sequence)) + { + $description->appendText(' in'); + $this->_sequence->describeTo($description); + } + + if (isset($this->_statePredicate)) + { + $description->appendText(' when'); + $this->_statePredicate->describeTo($description); + } + + if (isset($this->_action)) + { + $this->_action->describeTo($description); + } + + $this->describeSatisfaction($description); + } + + // -- Private methods + + private function _getInvocationSignature() + { + $class = Yay_MockGenerator::getInstance() + ->reverseNamingScheme(get_class($this->_object)); + if (isset($this->_method)) + { + $method = $this->_method; + } + else + { + $method = ''; + } + if (!empty($this->_matchers)) + { + $args = array(); + foreach ($this->_matchers as $matcher) + { + $args[] = $matcher->describeMatch('%s [%s]'); + } + $params = implode(', ', $args); + } + else + { + $params = ''; + } + return sprintf('%s::%s(%s)', $class, $method, $params); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtLeastExpectation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtLeastExpectation.php new file mode 100644 index 0000000..eb167be --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtLeastExpectation.php @@ -0,0 +1,101 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which wants at least a set number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_AtLeastExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The expected Invocation count. + * @var int + * @access private + */ + private $_count = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new AtLeastExpectation expecting at least $n Invocations. + * @param int $n + */ + public function __construct($n) + { + $this->_count = $n; + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return ($this->_matched >= $this->_count); + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + if ($this->_count > 0) + { + $description->appendText(sprintf('At least %d', $this->_count)); + } + else + { + $description->appendText('Any number'); + } + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_count) + { + $description->appendText(' already'); + } + $description->appendText(sprintf(' occurred %d times', $this->_matched)); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtMostExpectation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtMostExpectation.php new file mode 100644 index 0000000..61d22fc --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/AtMostExpectation.php @@ -0,0 +1,117 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which wants up to a set number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_AtMostExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The expected Invocation count. + * @var int + * @access private + */ + private $_count = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new AtMostExpectation expecting at most $n Invocations. + * @param int $n + */ + public function __construct($n) + { + $this->_count = $n; + } + + /** + * Test if this Invocation is one that was expected by this Expectation. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + return parent::isExpected($invocation) && ($this->_matched <= $this->_count); + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return true; + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + if ($this->_count > 0) + { + $description->appendText(sprintf('At most %d', $this->_count)); + } + else + { + $description->appendText('No invocations'); + } + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_count) + { + $description->appendText(' already'); + } + $description->appendText( + sprintf( + ' occurred %d times', + (($this->_matched < $this->_count) + ? $this->_matched + : $this->_count) + )); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/BetweenExpectation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/BetweenExpectation.php new file mode 100644 index 0000000..0368746 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/BetweenExpectation.php @@ -0,0 +1,118 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which allows a boundary for a number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_BetweenExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The minimum Invocation count. + * @var int + * @access private + */ + private $_min = 0; + + /** + * The maximum Invocation count. + * @var int + * @access private + */ + private $_max = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new BetweenExpectation expecting between $min and $max Invocations. + * @param int $n + */ + public function __construct($min, $max) + { + $this->_min = $min; + $this->_max = $max; + } + + /** + * Test if this Invocation is one that was expected by this Expectation. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + return parent::isExpected($invocation) && ($this->_matched <= $this->_max); + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return ($this->_matched >= $this->_min); + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + $description->appendText(sprintf('Between %d and %d', $this->_min, $this->_max)); + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_min) + { + $description->appendText(' already'); + } + $description->appendText( + sprintf( + ' occurred %d times', + (($this->_matched < $this->_max) + ? $this->_matched + : $this->_max) + )); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/ExactlyExpectation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/ExactlyExpectation.php new file mode 100644 index 0000000..243fe46 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Expectations/ExactlyExpectation.php @@ -0,0 +1,117 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which wants an exact number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_ExactlyExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The expected Invocation count. + * @var int + * @access private + */ + private $_count = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new ExactlyExpectation expecting $n Invocations. + * @param int $n + */ + public function __construct($n) + { + $this->_count = $n; + } + + /** + * Test if this Invocation is one that was expected by this Expectation. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + return parent::isExpected($invocation) && ($this->_matched <= $this->_count); + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return ($this->_matched >= $this->_count); + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + if ($this->_count > 0) + { + $description->appendText(sprintf('Exactly %d', $this->_count)); + } + else + { + $description->appendText('No invocations'); + } + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_count) + { + $description->appendText(' already'); + } + $description->appendText( + sprintf( + ' occurred %d times', + (($this->_matched < $this->_count) + ? $this->_matched + : $this->_count) + )); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Invocation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Invocation.php new file mode 100644 index 0000000..d382157 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Invocation.php @@ -0,0 +1,49 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * A representation of a Method invocation. + * This is a container for the object the method was invoked on, the method- + * name and the arguments in the invocation. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Invocation extends Yay_SelfDescribing +{ + + /** + * Get the object which this Invocation occured on. + * @return object + */ + public function getObject(); + + /** + * Get the method name of the invoked method. + * @return string + */ + public function getMethod(); + + /** + * Get the argument list in the Invocation. + * @return array + */ + public function &getArguments(); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationHandler.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationHandler.php new file mode 100644 index 0000000..a669fe5 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationHandler.php @@ -0,0 +1,36 @@ +. + + */ + +//require 'Yay/Invocation.php'; + +/** + * Listens for Invocations and returns a suitable value. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_InvocationHandler +{ + + /** + * Handle the given $invocation and return a value for it. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &handleInvocation(Yay_Invocation $invocation); + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationProxy.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationProxy.php new file mode 100644 index 0000000..e8d0aa9 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationProxy.php @@ -0,0 +1,90 @@ +. + + */ + +//require 'Yay/ExpectationProvider.php'; +//require 'Yay/InvocationRecorder.php'; +//require 'Yay/MockObject.php'; +//require 'Yay/SimpleInvocation.php'; + +/** + * Proxies Invocations on a Mock object to the recorder. + * @author Chris Corbyn + * @package Yay + */ +class Yay_InvocationProxy implements Yay_ExpectationProvider +{ + + /** + * The InvocationRecorder which is using this InvocationProxy. + * @var Yay_InvocationRecorder + * @access private + */ + private $_recorder; + + /** + * The Mock object where Invocations are recorded from. + * @var Yay_MockObject + * @access private + */ + private $_mock; + + /** + * Create a new InvocationProxy for $recorder and $mock. + * @param Yay_InvocationRecorder $recorder + * @param Yay_MockObject $mock + */ + public function __construct(Yay_InvocationRecorder $recorder, Yay_MockObject $mock) + { + $this->_recorder = $recorder; + $this->_mock = $mock; + } + + /** + * Direct all invocations to the recorder. + * @param string $method + * @param array $args + * @return Yay_InvocationRecorder + */ + public function __call($method, $args) + { + if (is_callable(array($this->_mock, $method))) + { + $invocation = new Yay_SimpleInvocation($this->_mock, $method, $args); + $this->_recorder->recordInvocation($invocation); + return $this->_recorder; + } + elseif (is_callable(array($this->_recorder, $method))) + { + return call_user_func_array(array($this->_recorder, $method), $args); + } + else + { + throw new BadMethodCallException('Mock method ' . $method . ' does not exist'); + } + } + + /** + * Returns the Expectation list. + * @return array of Yay_Expectation + */ + public function getExpectations() + { + return $this->__call(__FUNCTION__, array()); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationRecorder.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationRecorder.php new file mode 100644 index 0000000..f3abed3 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/InvocationRecorder.php @@ -0,0 +1,36 @@ +. + + */ + +//require 'Yay/Invocation.php'; +//require 'Yay/ExpectationProvider.php'; + +/** + * Listens for Invocations and provides expectations based on them. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_InvocationRecorder extends Yay_ExpectationProvider +{ + + /** + * Record the given $invocation. + * @param Yay_Invocation $invocation + */ + public function recordInvocation(Yay_Invocation $invocation); + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matcher.php new file mode 100644 index 0000000..5dc3e74 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matcher.php @@ -0,0 +1,48 @@ +. + + */ + +/** + * The Matcher interface for comparing arguments. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Matcher +{ + + /** + * Compare the $argument with whatever is expected to match it. + * @param mixed $argument + * @return boolean + */ + public function matches(&$argument); + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional(); + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/AnyMatcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/AnyMatcher.php new file mode 100644 index 0000000..8592ba3 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/AnyMatcher.php @@ -0,0 +1,85 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Allows anything to match. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_AnyMatcher implements Yay_Matcher +{ + + /** + * A type to compare with. + * @var string + * @access private + */ + private $_type; + + /** + * The desired result. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new AnyMatcher, optionally constrained only to objects of $type. + * @param string $type, optional + * @param boolean $result + */ + public function __construct($type = null, $result = true) + { + $this->_type = $type; + $this->_result = $result; + } + + /** + * Always returns true where no type is given, and where the type matches otherwise. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = (is_null($this->_type) || ($value instanceof $this->_type)); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return 'ANYTHING'; + } + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/BoundsMatcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/BoundsMatcher.php new file mode 100644 index 0000000..57aef64 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/BoundsMatcher.php @@ -0,0 +1,94 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values to test if they are within given boundaries. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_BoundsMatcher implements Yay_Matcher +{ + + /** + * The upper bound. + * @var mixed + * @access private + */ + private $_upper; + + /** + * The lower bound. + * @var mixed + * @access private + */ + private $_lower; + + /** + * The desired result. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new BoundsMatcher between $lower and $upper. + * @param mixed $lower + * @param mixed $upper + * @param boolean $result which is wanted + */ + public function __construct($lower, $upper, $result = true) + { + $this->_upper = $upper; + $this->_lower = $lower; + $this->_result = $result; + } + + /** + * Compare $value with the boundaries and return true if it is within them. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = ($value <= $this->_upper && $value >= $this->_lower); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return sprintf($format, 'between', $this->_min . ' and ' . $this->_max); + } + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/EqualMatcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/EqualMatcher.php new file mode 100644 index 0000000..1d4f25d --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/EqualMatcher.php @@ -0,0 +1,50 @@ +. + + */ + +//require 'Yay/Matchers/IdenticalMatcher.php'; + +/** + * Compares values for equality. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_EqualMatcher extends Yay_Matchers_IdenticalMatcher +{ + + /** + * Create a new EqualMatcher expecting $expected. + * @param mixed $expected + * @param boolean $result to be expected + */ + public function __construct($expected, $result = true) + { + parent::__construct($expected, $result); + } + + /** + * Compare $value with the expected value and return true if it is equal. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = (($this->_expected == $value) && ($value == $this->_expected)); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/IdenticalMatcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/IdenticalMatcher.php new file mode 100644 index 0000000..c227b67 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/IdenticalMatcher.php @@ -0,0 +1,116 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values for value and type. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_IdenticalMatcher implements Yay_Matcher +{ + + /** + * The expected value. + * @var mixed + * @access protected + */ + protected $_expected; + + /** + * The expected return value. + * @var boolean + * @access protected + */ + protected $_result; + + /** + * Create a new IdenticalMatcher expecting $expected. + * @param mixed $expected + * @param boolean $result to be expected + */ + public function __construct($expected, $result = true) + { + $this->_expected = $expected; + $this->_result = $result; + } + + /** + * Compare $value with the expected value and return true if it matches in + * type and in value. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = (($this->_expected === $value) && ($value === $this->_expected)); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + $description = ''; + $value = $this->_expected; + if (is_int($value)) + { + $description = sprintf($format, 'int', $value); + } + elseif (is_float($value)) + { + $description = sprintf($format, 'float', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_numeric($value)) + { + $description = sprintf($format, 'number', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_string($value)) + { + $description = sprintf($format, 'string', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_object($value)) + { + $description = sprintf($format, 'object', get_class($value)); + } + elseif (is_array($value)) + { + $description = sprintf($format, 'array', count($value) . ' items'); + } + else + { + $description = sprintf($format, gettype($value), preg_replace('/^(.{8}).+/', '$1..', (string) $value)); + } + return $description; + } + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/OptionalMatcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/OptionalMatcher.php new file mode 100644 index 0000000..a348d57 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/OptionalMatcher.php @@ -0,0 +1,103 @@ +. + + */ + +//require 'Yay/Matcher.php'; +//require 'Yay/Matchers/IdenticalMatcher.php'; + +/** + * Wraps Matchers and makes them Optional. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_OptionalMatcher implements Yay_Matcher +{ + + /** + * A matcher to delegate to. + * @var Yay_Matcher + * @access private + */ + private $_matcher; + + /** + * Create a new OptionalMatcher, optionally wrapping $value. + * @param mixed $value, optional + */ + public function __construct($value = null) + { + if (isset($value)) + { + if ($value instanceof Yay_Matcher) + { + $this->_matcher = $value; + } + else + { + $this->_matcher = new Yay_Matchers_IdenticalMatcher($value); + } + } + } + + /** + * Returns true if no matcher set, otherwise it delegates to the given Matcher. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + if (isset($this->_matcher)) + { + $matches = $this->_matcher->matches($value); + } + else + { + $matches = true; + } + return $matches; + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return true; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + $name = 'optional'; + if (isset($this->_matcher)) + { + $value = $this->_matcher->describeMatch($format); + } + else + { + $value = '*'; + } + return sprintf($format, $name, $value); + } + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/PatternMatcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/PatternMatcher.php new file mode 100644 index 0000000..4f4043f --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/PatternMatcher.php @@ -0,0 +1,88 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values against a PCRE pattern. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_PatternMatcher implements Yay_Matcher +{ + + /** + * The expected pattern. + * @var string + * @access private + */ + private $_pattern; + + /** + * The desired return value. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new PatternMatcher expecting $pattern. + * @param string $pattern + * @param boolean $result to be expected + */ + public function __construct($pattern, $result = true) + { + $this->_pattern = $pattern; + $this->_result = $result; + } + + /** + * Compare $value with the expected pattern and return true if it matches. + * @param string $value + * @return boolean + */ + public function matches(&$value) + { + $return = ( + (is_string($value) || is_numeric($value)) + && preg_match($this->_pattern, $value) + ); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return sprintf($format, 'pattern', $this->_pattern); + } + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/ReferenceMatcher.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/ReferenceMatcher.php new file mode 100644 index 0000000..cae6a08 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Matchers/ReferenceMatcher.php @@ -0,0 +1,104 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values to see if they reference one another. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_ReferenceMatcher implements Yay_Matcher +{ + + /** + * The expected reference. + * @var mixed + * @access private + */ + private $_ref; + + /** + * The desired return value. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new IdenticalMatcher expecting $expected. + * @param mixed $expected + * @param boolean $result to be expected + */ + public function __construct(&$ref, $result = true) + { + $this->_ref =& $ref; + $this->_result = $result; + } + + /** + * Compare $ref with the expected reference and return true if it is the same reference. + * @param mixed $ref + * @return boolean + */ + public function matches(&$ref) + { + if (is_object($ref)) + { + $isRef = ($this->_ref === $ref); + } + else + { + if ($this->_ref === $ref) + { + $copy = $ref; + $randomString = uniqid('yay'); + $ref = $randomString; + $isRef = ($this->_ref === $ref); + $ref = $copy; + } + else + { + $isRef = false; + } + } + + return (($this->_result && $isRef) || (!$this->_result && !$isRef)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return '[reference]'; + } + +} \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/MockGenerator.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/MockGenerator.php new file mode 100644 index 0000000..b3a9c78 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/MockGenerator.php @@ -0,0 +1,323 @@ +. + + */ + +/** + * Generates the code for a Mock object. + * This lives as a singleton for a few reasons. + * @author Chris Corbyn + * @package Yay + */ +class Yay_MockGenerator +{ + + /** The name of the Mock object interface */ + const MOCK_INTERFACE = 'Yay_MockObject'; + + /** Prefixed to types to create a Mock name */ + const MOCK_PREFIX = 'Yay_MockObjects_'; + + /** Singleton instance */ + private static $_instance = null; + + /** + * The path a template which draws a Mock. + * @var string + * @access private + */ + private $_template; + + /** + * A map of mocked type hints to their concrete class names. + * @var array + * @access private + */ + private $_mocked = array(); + + /** + * Constructor cannot be used. + */ + private function __construct() + { + } + + /** + * Get a singleton instance of this MockGenerator. + * @return Yay_MockGenerator + */ + public static function getInstance() + { + if (is_null(self::$_instance)) + { + self::$_instance = new self(); + } + return self::$_instance; + } + + /** + * Set the path to a template which can draw a mock class. + * @param string $path + */ + public function setMockTemplate($path) + { + $this->_template = $path; + } + + /** + * Produce class code for a MockObject of $typeHint and return its concrete name. + * @param string $typeHint + * @return string + */ + public function generateMock($typeHint) + { + if (!$className = $this->_getConcreteMockName($typeHint)) + { + $className = $this->_materializeMockCode($typeHint); + } + return $className; + } + + /** + * Use a fixed naming scheme to make a mock class name from $typeHint. + * @param string $typeHint + * @return string + */ + public function applyNamingScheme($typeHint) + { + return self::MOCK_PREFIX . $typeHint; + } + + /** + * Remove any adjustments that were made to an original type hint. + * @param string $typeHint + * @return string + */ + public function reverseNamingScheme($typeHint) + { + $len = strlen(self::MOCK_PREFIX); + if (substr($typeHint, 0, $len) == self::MOCK_PREFIX) + { + $typeHint = substr($typeHint, $len); + } + return $typeHint; + } + + // -- Private methods + + /** + * Try to lookup a mocked concrete class name for $typeHint. + * @param string $typeHint + * @return string + * @access private + */ + private function _getConcreteMockName($typeHint) + { + if (array_key_exists($typeHint, $this->_mocked)) + { + return $this->_mocked[$typeHint]; + } + } + + /** + * Produce the mock object code and return its name. + * @param string $typeHint + * @return string + * @access private + */ + private function _materializeMockCode($typeHint) + { + $reflector = new ReflectionClass($typeHint); + $mockData = array( + 'className' => $this->applyNamingScheme($typeHint), + 'extends' => $this->_getSuperclass($reflector), + 'interfaces' => $this->_getInterfaces($reflector), + 'methods' => $this->_getMethods($reflector) + ); + + extract($mockData); + $code = include($this->_template); + eval($code); + + $this->_mocked[$typeHint] = $mockData['className']; + return $mockData['className']; + } + + /** + * Get all known interfaces for $reflector. + * @param ReflectionClass $reflector + * @return array + * @access private + */ + private function _getInterfaces(ReflectionClass $reflector) + { + $interfaces = array(); + if ($reflector->isInterface()) + { + if ($reflector->getName() != self::MOCK_INTERFACE) + { + $interfaces[] = $reflector->getName(); + } + } + else + { + foreach ($reflector->getInterfaces() as $interfaceReflector) + { + if ($interfaceReflector->getName() != self::MOCK_INTERFACE) + { + $interfaces[] = $interfaceReflector->getName(); + } + } + } + return $interfaces; + } + + /** + * Get the superclass this mock object needs to extend. + * @param ReflectionClass $reflector + * @return string + * @access private + */ + private function _getSuperclass(ReflectionClass $reflector) + { + if ($this->_canExtend($reflector)) + { + $superclass = $reflector->getName(); + } + else + { + $superclass = ''; + } + return $superclass; + } + + /** + * Get all methods from $reflector. + * @param ReflectionClass $reflector + * @return array + * @access private + */ + private function _getMethods(ReflectionClass $reflector) + { + $methods = array(); + foreach ($reflector->getMethods() as $reflectionMethod) + { + if ($reflectionMethod->isConstructor() + || $reflectionMethod->getName() == '__clone') + { + continue; + } + if ($reflectionMethod->isPublic() || $reflectionMethod->isProtected()) + { + $methods[] = array( + 'name' => $reflectionMethod->getName(), + 'access' => $reflectionMethod->isPublic() ? 'public' : 'protected', + 'modifiers' => $reflectionMethod->isStatic() ? 'static' : '', + 'returnReference' => $reflectionMethod->returnsReference(), + 'parameters' => $this->_getParameters($reflectionMethod) + ); + } + } + return $methods; + } + + /** + * Get all parameters for $method. + * @param ReflectionMethod $method + * @return array + * @access private + */ + private function _getParameters(ReflectionMethod $method) + { + $parameters = array(); + foreach ($method->getParameters() as $reflectionParameter) + { + $hint = ''; + if ($reflectionParameter->isArray()) + { + $hint = 'array'; + } + elseif ($c = $reflectionParameter->getClass()) + { + $hint = $c->getName(); + } + $parameters[] = array( + 'hint' => $hint, + 'byReference' => $reflectionParameter->isPassedByReference(), + 'optional' => $reflectionParameter->isOptional() + ); + } + return $parameters; + } + + /** + * Determine if the reflector for the given class is safe to extend. + * @param ReflectionClass $reflector + * @return boolean + * @access private + */ + private function _canExtend(ReflectionClass $reflector) + { + $canExtend = true; + $warning = false; + if ($reflector->isInterface()) + { + $canExtend = false; + } + else + { + if ($constructor = $reflector->getConstructor()) + { + if ($constructor->isPrivate() || $constructor->isFinal()) + { + $canExtend = false; + $warning = 'has a private or final constructor'; + } + } + elseif ($reflector->isFinal()) + { + $canExtend = false; + $warning = 'is declared final'; + } + else + { + foreach ($reflector->getMethods() as $method) + { + if (($method->isPublic() || $method->isProtected()) && $method->isFinal()) + { + $canExtend = false; + $warning = 'contains final methods'; + } + } + } + } + if ($warning) + { + trigger_error( + sprintf('The type [%s] to be mocked %s.' . + ' Mocking classes which cannot be fully overridden results' . + ' in a loss of class type. It is safe to supress this warning if you' . + ' are aware of the conflict.', + $reflector->getName(), + $warning + ), + E_USER_WARNING + ); + } + return $canExtend; + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/MockObject.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/MockObject.php new file mode 100644 index 0000000..3c65de0 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/MockObject.php @@ -0,0 +1,25 @@ +. + + */ + +/** + * A tag placed on any MockObjects generated in the context of YayMock. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_MockObject { +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Mockery.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Mockery.php new file mode 100644 index 0000000..38d2410 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Mockery.php @@ -0,0 +1,183 @@ +. + + */ + +//require 'Yay/MockGenerator.php'; +//require 'Yay/SimpleInvocation.php'; +//require 'Yay/SimpleDescription.php'; +//require 'Yay/InvocationHandler.php'; +//require 'Yay/MockObject.php'; +//require 'Yay/ExpectationProvider.php'; +//require 'Yay/NotSatisfiedException.php'; +//require 'Yay/StateMachine.php'; +//require 'Yay/SimpleSequence.php'; + +/** + * The main Yay context. + * Handles the generation of MockObjects and the Invocation of methods. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Mockery implements Yay_InvocationHandler +{ + + /** + * The Expectation stack which is being checked. + * @var array + * @access private + */ + private $_expectations = array(); + + /** + * Invocations which are not expected by any Expectations get caught here. + * @var array + * @access private + */ + private $_unexpectedInvocations = array(); + + /** + * A mock class generator. + * @var Yay_MockGenerator + * @access private + */ + private $_generator; + + /** + * Create a new Mockery. + */ + public function __construct() + { + $this->_generator = Yay_MockGenerator::getInstance(); + } + + /** + * Create a MockObject matching $typeHint. + * If the $typeHint is an interface the Mock will implement the interface + * and maintain the method signatures from that interface. + * If the $typeHint is a class name the Mock will extend the class overriding + * all public methods (HOWEVER, if the class contains final methods it is not + * possible to override all methods and hence, the mock will have no specific + * type. + * @param string $typeHint + * @return Yay_MockObject + */ + public function mock($typeHint) + { + $className = $this->_generator->generateMock($typeHint); + $reflector = new ReflectionClass($className); + return $reflector->newInstance($this); + } + + /** + * Specify an Expectation (or Expectations) to check. + * @param Yay_ExpectationProvider $provider + */ + public function checking(Yay_ExpectationProvider $provider) + { + foreach ($provider->getExpectations() as $expectation) + { + $this->_expectations[] = $expectation; + } + } + + /** + * Get a state machine named $name. + * @param string $name + * @return Yay_States + */ + public function states($name) + { + return new Yay_StateMachine($name); + } + + /** + * Create a new Sequence named $name. + * @param string $name + * @return Yay_Sequence + */ + public function sequence($name) + { + return new Yay_SimpleSequence($name); + } + + /** + * Used by YayMock internally (ignore this method!). + */ + public function &handleInvocation(Yay_Invocation $invocation) + { + $ret = null; + $expected = false; + foreach ($this->_expectations as $expectation) + { + if ($expectation->isExpected($invocation)) + { + $expected = true; + if ($action = $expectation->getAction($invocation)) + { + $ret =& $action->invoke($invocation); + } + break; + } + } + if (!$expected) + { + $this->_unexpectedInvocations[] = $invocation; + } + return $ret; + } + + /** + * Assert that all Expectations are satisfied. + * Throws an Exception of type Yay_NotSatisfiedException if any Expecations + * are not satisfied. + * @throws Yay_NotSatisfiedException + */ + public function assertIsSatisfied() + { + $description = new Yay_SimpleDescription(); + $satisfied = true; + foreach ($this->_unexpectedInvocations as $invocation) + { + $description->appendText('Unexpected invocation'); + $invocation->describeTo($description); + $description->appendText(PHP_EOL); + $satisfied = false; + } + if (!$satisfied) + { + $description->appendText(PHP_EOL); + } + foreach ($this->_expectations as $expectation) + { + if (!$expectation->isSatisfied()) + { + $description->appendText('* '); + $satisfied = false; + } + $expectation->describeTo($description); + $description->appendText(PHP_EOL); + } + if (!$satisfied) + { + throw new Yay_NotSatisfiedException( + 'Not all expectations were satisfied or a method was invoked unexpectedly.' . + PHP_EOL . PHP_EOL . $description->toString() . PHP_EOL + ); + } + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/NotSatisfiedException.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/NotSatisfiedException.php new file mode 100644 index 0000000..8a76daa --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/NotSatisfiedException.php @@ -0,0 +1,36 @@ +. + + */ + +/** + * The Exception thrown by Context::assertIsSatisfied() if assertion fails. + * @author Chris Corbyn + * @package Yay + */ +class Yay_NotSatisfiedException extends Exception +{ + + /** + * Create a new NotSatisfiedException with $message. + * @param string $message + */ + public function __construct($message) + { + parent::__construct($message); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/SelfDescribing.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SelfDescribing.php new file mode 100644 index 0000000..186249a --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SelfDescribing.php @@ -0,0 +1,35 @@ +. + + */ + +//require 'Yay/Description.php'; + +/** + * Components implementing this can describe what they do to a Description instance. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_SelfDescribing +{ + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/Sequence.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Sequence.php new file mode 100644 index 0000000..225b2a5 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/Sequence.php @@ -0,0 +1,43 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * Provides a means for Expectations to verify they are called in the correct order. + * This allows Invocations to be forced in a particular order. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Sequence extends Yay_SelfDescribing +{ + + /** + * Ask for a new Sequence Id and register the new sequence. + * @return int $id + */ + public function requestSequenceId(); + + /** + * Check if the sequence has progressed far enough for this sequence ID to be used. + * @param int $id + * @return boolean + */ + public function isInSequence($sequenceId); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleDescription.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleDescription.php new file mode 100644 index 0000000..f86b4b7 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleDescription.php @@ -0,0 +1,63 @@ +. + + */ + +//require 'Yay/Description.php'; + +/** + * A basic Description container for error messages. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleDescription implements Yay_Description +{ + + /** + * An internal text buffer. + * @var string + * @access private + */ + private $_text = ''; + + /** + * Append an existing Description to this Description. + * @param Yay_Description + */ + public function appendDescription(Yay_Description $description) + { + $this->_text .= $description->toString(); + } + + /** + * Append text content to this Description. + * @param string $text + */ + public function appendText($text) + { + $this->_text .= $text; + } + + /** + * Get this description back as a formatted string. + * @return string + */ + public function toString() + { + return $this->_text; + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleInvocation.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleInvocation.php new file mode 100644 index 0000000..012b568 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleInvocation.php @@ -0,0 +1,163 @@ +. + + */ + +//require 'Yay/Invocation.php'; +//require 'Yay/MockGenerator.php'; + +/** + * The standard implementation of the Invocation interface. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleInvocation implements Yay_Invocation +{ + + /** + * The Object on which the Inovation occurred. + * @var object + * @access private + */ + private $_object; + + /** + * The method name invoked. + * @var string + * @access private + */ + private $_method; + + /** + * The arguments in the Invocation. + * @var array + * @access private + */ + private $_arguments; + + /** + * Create a new SimpleInvocation with the given details. + * @param object $object + * @param string $method + * @param array &$arguments + */ + public function __construct($object, $method, array &$arguments) + { + $this->_object = $object; + //Massage __call() overloading so the interface is tested correctly + if ($method == '__call') + { + $method = array_shift($arguments); + $args =& array_shift($arguments); + $arguments =& $args; + } + $this->_method = $method; + $this->_arguments =& $arguments; + } + + /** + * Get the object which this Invocation occured on. + * @return object + */ + public function getObject() + { + return $this->_object; + } + + /** + * Get the method name of the invoked method. + * @return string + */ + public function getMethod() + { + return $this->_method; + } + + /** + * Get the argument list in the Invocation. + * @return array + */ + public function &getArguments() + { + return $this->_arguments; + } + + /** + * Describe this Invocation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' of %s;', $this->_getInvocationSignature())); + } + + // -- Private methods + + private function _getInvocationSignature() + { + $class = Yay_MockGenerator::getInstance() + ->reverseNamingScheme(get_class($this->_object)); + if (!empty($this->_arguments)) + { + $args = array(); + foreach ($this->_arguments as $arg) + { + $args[] = $this->_describeArgument($arg, '%s [%s]'); + } + $params = implode(', ', $args); + } + else + { + $params = ''; + } + return sprintf('%s::%s(%s)', $class, $this->_method, $params); + } + + private function _describeArgument($arg, $format) + { + $description = ''; + if (is_int($arg)) + { + $description = sprintf($format, 'int', $arg); + } + elseif (is_float($arg)) + { + $description = sprintf($format, 'float', preg_replace('/^(.{8}).+/', '$1..', $arg)); + } + elseif (is_numeric($arg)) + { + $description = sprintf($format, 'number', preg_replace('/^(.{8}).+/', '$1..', $arg)); + } + elseif (is_string($arg)) + { + $description = sprintf($format, 'string', preg_replace('/^(.{8}).+/', '$1..', $arg)); + } + elseif (is_object($arg)) + { + $description = sprintf($format, 'object', get_class($arg)); + } + elseif (is_array($arg)) + { + $description = sprintf($format, 'array', count($arg) . ' items'); + } + else + { + $description = sprintf($format, gettype($arg), preg_replace('/^(.{8}).+/', '$1..', (string) $arg)); + } + return $description; + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleSequence.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleSequence.php new file mode 100644 index 0000000..016c30a --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleSequence.php @@ -0,0 +1,108 @@ +. + + */ + +/** + * Provides a means for Expectations to verify they are called in the correct order. + * This allows Invocations to be forced in a particular order. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleSequence implements Yay_Sequence +{ + + /** + * The name of this sequence. + * @var string + * @access private + */ + private $_name; + + /** + * The list of sequence IDs expected. + * @var array + * @access private + */ + private $_sequenceIds = array(); + + /** + * An internal sequence counter. + * @var int + * @access private + */ + private $_counter = 0; + + /** + * The current position in the sequence. + * @var int + * @access private + */ + private $_currentId = null; + + /** + * Create a new Sequence with $name. + * @param string $name + */ + public function __construct($name) + { + $this->_name = $name; + } + + /** + * Ask for a new Sequence Id and register the new sequence. + * @return int $id + */ + public function requestSequenceId() + { + $id = $this->_counter++; + $this->_sequenceIds[] = $id; + return $id; + } + + /** + * Check if the sequence has progressed far enough for this sequence ID to be used. + * @param int $id + * @return boolean + */ + public function isInSequence($sequenceId) + { + if ($this->_currentId === $sequenceId) + { + $inSequence = true; + } + elseif (current($this->_sequenceIds) === $sequenceId) + { + $this->_currentId = array_shift($this->_sequenceIds); + $inSequence = true; + } + else + { + $inSequence = false; + } + return $inSequence; + } + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' sequence %s;', $this->_name)); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleState.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleState.php new file mode 100644 index 0000000..18c7621 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleState.php @@ -0,0 +1,49 @@ +. + + */ + +//require 'Yay/State.php'; +//require 'Yay/States.php'; +//require 'Yay/SimpleStatePredicate.php'; + +/** + * A State from a State machine. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleState extends Yay_SimpleStatePredicate implements Yay_State +{ + + /** + * Create a new State for $stateMachine to be $stateName. + * @param Yay_States $stateMachine + * @param string $stateName + */ + public function __construct(Yay_States $stateMachine, $stateName) + { + parent::__construct($stateMachine, $stateName, true); + } + + /** + * Make this State active. + */ + public function activate() + { + $this->_stateMachine->become($this->_stateName); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleStatePredicate.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleStatePredicate.php new file mode 100644 index 0000000..6c9458c --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/SimpleStatePredicate.php @@ -0,0 +1,88 @@ +. + + */ + +//require 'Yay/StatePredicate.php'; +//require 'Yay/States.php'; + +/** + * An expectation about what State a state machine is in. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleStatePredicate implements Yay_StatePredicate +{ + + /** + * The state machine which this predicate checks. + * @var Yay_States + * @access private + */ + protected $_stateMachine; + + /** + * The state name to check for in the state machine. + * @var string + * @access private + */ + protected $_stateName; + + /** + * True if the state is wanted, false otherwise. + * @var boolean + * @access private + */ + private $_is = true; + + /** + * Create a new StatePredicate. + * @param Yay_States $stateMachine + * @param string $stateName to expect + * @param boolean $is (negation point) + */ + public function __construct(Yay_States $stateMachine, $stateName, $is = true) + { + $this->_stateMachine = $stateMachine; + $this->_stateName = $stateName; + $this->_is = $is; + } + + /** + * Return true if the state machine is in this state. + * @return boolean + */ + public function isActive() + { + return (($this->_is && $this->_stateMachine->getCurrentState() == $this->_stateName) + || (!$this->_is && $this->_stateMachine->getCurrentState() != $this->_stateName)); + } + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $this->_stateMachine->describeTo($description); + $description->appendText(sprintf( + ' %s %s;', + ($this->_is ? 'is' : 'is not'), + $this->_stateName + )); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/State.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/State.php new file mode 100644 index 0000000..045e8bb --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/State.php @@ -0,0 +1,34 @@ +. + + */ + +//require 'Yay/StatePredicate.php'; + +/** + * A State from a State machine. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_State extends Yay_StatePredicate +{ + + /** + * Make this State active. + */ + public function activate(); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/StateMachine.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/StateMachine.php new file mode 100644 index 0000000..2a9128a --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/StateMachine.php @@ -0,0 +1,113 @@ +. + + */ + +//require 'Yay/State.php'; +//require 'Yay/SimpleState.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/SimpleStatePredicate.php'; + +/** + * A basic state machine. + * @author Chris Corbyn + * @package Yay + */ +class Yay_StateMachine implements Yay_States +{ + + /** + * The name of this state machine. + * @var string + * @access private + */ + private $_name; + + /** + * The current state. + * @var string + * @access private + */ + private $_state; + + /** + * Create a new State machine with $name. + * @param string $name + */ + public function __construct($name) + { + $this->_name = $name; + } + + /** + * Set the initial state of this state machine. + * @param string $stateName + * @return Yay_States + */ + public function startsAs($stateName) + { + $this->become($stateName); + return $this; + } + + /** + * Get the state which puts the state machine into the named state. + * @param string $stateName + * @return Yay_State + */ + public function is($stateName) + { + return new Yay_SimpleState($this, $stateName); + } + + /** + * Get the predicate which indicates the state machine is NOT in the named state. + * @param string $stateName + * @return Yay_StatePredicate + */ + public function isNot($stateName) + { + return new Yay_SimpleStatePredicate($this, $stateName, false); + } + + /** + * Become the named state. + * @param string $stateName + */ + public function become($stateName) + { + $this->_state = $stateName; + } + + /** + * Get the name of the current state. + * @return string + */ + public function getCurrentState() + { + return $this->_state; + } + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' %s', $this->_name)); + } + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/StatePredicate.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/StatePredicate.php new file mode 100644 index 0000000..5941d76 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/StatePredicate.php @@ -0,0 +1,35 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * An expectation about what State a state machine is in. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_StatePredicate extends Yay_SelfDescribing +{ + + /** + * Return true if the state machine is in this state. + * @return boolean + */ + public function isActive(); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/classes/Yay/States.php b/lib/Swift/test-suite/lib/yaymock/classes/Yay/States.php new file mode 100644 index 0000000..c021c4f --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/classes/Yay/States.php @@ -0,0 +1,62 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * A basic state machine. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_States extends Yay_SelfDescribing +{ + + /** + * Set the initial state of this state machine. + * @param string $stateName + * @return Yay_States + */ + public function startsAs($stateName); + + /** + * Get the state which puts the state machine into the named state. + * @param string $stateName + * @return Yay_State + */ + public function is($stateName); + + /** + * Get the predicate which indicates the state machine is NOT in the named state. + * @param string $stateName + * @return Yay_StatePredicate + */ + public function isNot($stateName); + + /** + * Become the named state. + * @param string $stateName + */ + public function become($stateName); + + /** + * Get the name of the current state. + * @return string + */ + public function getCurrentState(); + +} diff --git a/lib/Swift/test-suite/lib/yaymock/mock.tpl.php b/lib/Swift/test-suite/lib/yaymock/mock.tpl.php new file mode 100644 index 0000000..937699e --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/mock.tpl.php @@ -0,0 +1,69 @@ +. + + */ + +?> + +class + + implements + + Yay_MockObject +{ + + private $_yayInvocationHandler; + + public function __construct(Yay_InvocationHandler $invocationHandler) + { + $this->_yayInvocationHandler = $invocationHandler; + } + + + function ( $param): ?> + 0) echo ','; ?> + + $arg_ + + ) + { + $value = null; + if (isset($this->_yayInvocationHandler)) + { + $args = array(); + for ($i = 0; $i < func_num_args(); ++$i) + { + $argName = 'arg_' . $i; + $args[] =& ${$argName}; + } + $invocation = new Yay_SimpleInvocation($this, __FUNCTION__, $args); + $value =& $this->_yayInvocationHandler->handleInvocation($invocation); + } + return $value; + } + + + public function __clone() + { + $this->_yayInvocationHandler = null; + } + +} + + \ No newline at end of file diff --git a/lib/Swift/test-suite/lib/yaymock/yay_convenience.php b/lib/Swift/test-suite/lib/yaymock/yay_convenience.php new file mode 100644 index 0000000..c145103 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/yay_convenience.php @@ -0,0 +1,176 @@ +. + + */ + +/** + * Provides non-namespaced classes and functions for brevity. + * Including this script is entirely optional. + * @author Chris Corbyn + * @package Yay + */ + +// Classes + +/** + * Allow occurences of Yay_Expectations::create() to be replaced with Expectations::create(). + */ +class Expectations extends Yay_Expectations { } + +/** + * Allows occurences of new Yay_Mockery() to be replaced with new Mockery(). + */ +class Mockery extends Yay_Mockery { } + +//Argument matchers + +/** + * Allows Yay::optional() to be called as optional(). + */ +function optional($value = null) +{ + return Yay::optional($value); +} + +/** + * Allows Yay::any() to be called as any(). + */ +function any($type = null) +{ + return Yay::any($type); +} + +/** + * Allows Yay::none() to be called as none(). + */ +function none($type = null) +{ + return Yay::none($type); +} + +/** + * Allows Yay::identical() to be called as identical(). + */ +function identical($value) +{ + return Yay::identical($value); +} + +/** + * Allows Yay::notIdentical() to be called as notIdentical(). + */ +function notIdentical($value) +{ + return Yay::notIdentical($value); +} + +/** + * Allows Yay::equal() to be called as equal(). + */ +function equal($value) +{ + return Yay::equal($value); +} + +/** + * Allows Yay::notEqual() to be called as notEqual(). + */ +function notEqual($value) +{ + return Yay::notEqual($value); +} + +/** + * Allows Yay::pattern() to be called as pattern(). + */ +function pattern($pattern) +{ + return Yay::pattern($pattern); +} + +/** + * Allows Yay::noPattern() to be called as noPattern(). + */ +function noPattern($pattern) +{ + return Yay::noPattern($pattern); +} + +/** + * Allows Yay::bounds() to be called as bounds(). + */ +function bounds($a, $b) +{ + return Yay::bounds($a, $b); +} + +/** + * Allows Yay::outside() to be called as outside(). + */ +function outside($a, $b) +{ + return Yay::outside($a, $b); +} + +/** + * Allows Yay::reference() to be called as reference(). + */ +function reference(&$ref) +{ + return Yay::reference($ref); +} + +/** + * Allows Yay::noReference() to be called as noReference(). + */ +function noReference(&$ref) +{ + return Yay::noReference($ref); +} + +//Actions + +/** + * Allows Yay::returnValue() to be called as returnValue(). + */ +function returnValue($value) +{ + return Yay::returnValue($value); +} + +/** + * Allows Yay::returnReference() to be called as returnReference(). + */ +function returnReference(&$ref) +{ + return Yay::returnReference($ref); +} + +/** + * Allows Yay::throwException() to be called as throwException(). + */ +function throwException(Exception $e) +{ + return Yay::throwException($e); +} + +/** + * Allows Yay::call() to be called as call(). + */ +function call($callback) +{ + return Yay::call($callback); +} diff --git a/lib/Swift/test-suite/lib/yaymock/yay_mock.php b/lib/Swift/test-suite/lib/yaymock/yay_mock.php new file mode 100644 index 0000000..7c76922 --- /dev/null +++ b/lib/Swift/test-suite/lib/yaymock/yay_mock.php @@ -0,0 +1,29 @@ +. + + */ + +/** + * YayMock include and bootstrap file. + * @author Chris Corbyn + */ + +require_once dirname(__FILE__) . '/classes/Yay.php'; +Yay::setClassPath(dirname(__FILE__) . '/classes'); +spl_autoload_register(array('Yay', 'autoload')); +Yay_MockGenerator::getInstance()->setMockTemplate(dirname(__FILE__) . '/mock.tpl.php'); + +//EOF diff --git a/lib/Swift/test-suite/run.php b/lib/Swift/test-suite/run.php new file mode 100644 index 0000000..fa67634 --- /dev/null +++ b/lib/Swift/test-suite/run.php @@ -0,0 +1,56 @@ +get('//./root/cimv2:Win32_Process.Handle="' . getmypid() . '"') + ->executablePath; + } +} + +$runner = new Sweety_Runner_CliRunner( + explode(PATH_SEPARATOR, SWEETY_TEST_PATH), + $exe . ' ' . $argv[0] + ); + +$name = !empty($argv[1]) ? $argv[1] : 'All Tests'; +$runner->setReporter(new Sweety_Reporter_CliReporter(sprintf('%s - %s', SWEETY_SUITE_NAME, $name))); + +$runner->setIgnoredClassRegex(SWEETY_IGNORED_CLASSES); + +$locators = preg_split('/\s*,\s*/', SWEETY_TEST_LOCATOR); +foreach ($locators as $locator) +{ + $runner->registerTestLocator(new $locator()); +} + +if (!empty($argv[1]) && !preg_match('~!?/.*?/~', $argv[1])) +{ + $testName = $argv[1]; + $format = !empty($argv[2]) ? $argv[2] : Sweety_Runner::REPORT_TEXT; + + $runner->runTestCase($testName, $format); +} +else +{ + $runner->runAllTests(); +} + diff --git a/lib/Swift/test-suite/sweety.js b/lib/Swift/test-suite/sweety.js new file mode 100644 index 0000000..663ff94 --- /dev/null +++ b/lib/Swift/test-suite/sweety.js @@ -0,0 +1,471 @@ +/* + JavaScript wrapper around REST API in Sweety. + */ + +/** + * A convenience class for using XPath. + * @author Chris Corbyn + * @constructor + */ +function SweetyXpath() { + + /** + * Get the first node matching the given expression. + * @param {String} expr + * @param {Element} node + * @returns Element + */ + this.getFirstNode = function getFirstNode(expr, node) { + var firstNode = _getRootNode(node).evaluate( + expr, node, _getNsResolver(node), XPathResult.FIRST_ORDERED_NODE_TYPE, null); + return firstNode.singleNodeValue; + }, + + /** + * Get all nodes matching the given expression. + * The returned result is a Node Snapshot. + * @param {String} expr + * @param {Element} node + * @returns Element[] + */ + this.getNodes = function getNodes(expr, node) { + var nodes = _getRootNode(node).evaluate( + expr, node, _getNsResolver(node), XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + var nodeSet = new Array(); + for (var i = 0, len = nodes.snapshotLength; i < len; i++) { + nodeSet.push(nodes.snapshotItem(i)); + } + return nodeSet; + }, + + /** + * Get the string value of the node matching the given expression. + * @param {String} expr + * @param {Element} node + * @returns String + */ + this.getValue = function getValue(expr, node) { + return _getRootNode(node).evaluate( + expr, node, _getNsResolver(node), XPathResult.STRING_TYPE, null).stringValue; + } + + /** + * Get the root node from which run evaluate. + * @param {Element} node + * @returns Element + */ + var _getRootNode = function _getRootNode(node) { + if (node.ownerDocument && node.ownerDocument.evaluate) { + return node.ownerDocument; + } else { + if (node.evaluate) { + return node; + } else { + return document; + } + } + } + + /** + * Get the NS Resolver used when searching. + * @param {Element} node + * @returns Element + */ + var _getNsResolver = function _getNsResolver(node) { + if (!document.createNSResolver) { + return null; + } + + if (node.ownerDocument) { + return document.createNSResolver(node.ownerDocument.documentElement); + } else { + return document.createNSResolver(node.documentElement); + } + } + +} + +/** + * The reporter interface so Sweety can tell the UI what's happening. + * @author Chris Corbyn + * @constructor + */ +function SweetyReporter() { //Interface/Base Class + + var _this = this; + + /** + * Create a sub-reporter for an individual test case. + * @param {String} testCaseName + * @returns SweetyReporter + */ + this.getReporterFor = function getReporterFor(testCaseName) { + return _this; + } + + /** + * Start reporting. + */ + this.start = function start() { + } + + /** + * Handle a skipped test case. + * @param {String} message + * @param {String} path + */ + this.reportSkip = function reportSkip(message, path) { + } + + /** + * Handle a passing assertion. + * @param {String} message + * @param {String} path + */ + this.reportPass = function reportPass(message, path) { + } + + /** + * Handle a failing assertion. + * @param {String} message + * @param {String} path + */ + this.reportFail = function reportFail(message, path) { + } + + /** + * Handle an unexpected exception. + * @param {String} message + * @param {String} path + */ + this.reportException = function reportException(message, path) { + } + + /** + * Handle miscellaneous test output. + * @param {String} output + * @param {String} path + */ + this.reportOutput = function reportOutput(output, path) { + } + + /** + * Finish reporting. + */ + this.finish = function finish() { + } + +} + + +/** + * Represents a single test case being run. + * @author Chris Corbyn + * @constructor + */ +function SweetyTestCaseRun(testClass, reporter) { + + var _this = this; + + /** The XMLHttpRequest used in testing */ + var _req; + + /** XPath handler */ + var _xpath = new SweetyXpath(); + + /** Callback function for completion event */ + this.oncompletion = function oncompletion() { + } + + /** + * Run this test. + */ + this.run = function run() { + if (!reporter.isStarted()) { + reporter.start(); + } + _req = _createHttpRequest(); + + if (!_req) { + return; + } + + _req.open("GET", "?test=" + testClass + "&format=xml", true); + _req.onreadystatechange = _handleXml; + _req.send(null); + } + + /** + * Get an XmlHttpRequest instance, cross browser compatible. + * @return Object + */ + var _createHttpRequest = function _createHttpRequest() { + var req = false; + + if (window.XMLHttpRequest && !(window.ActiveXObject)) { + try { + req = new XMLHttpRequest(); + } catch(e) { + req = false; + } + } else if (window.ActiveXObject) { + try { + req = new ActiveXObject("Msxml2.XMLHTTP"); + } catch(e) { + try { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) { + req = false; + } + } + } + + return req; + } + + /** + * Handle the XML response from the test. + */ + var _handleXml = function _handleXml() { + if (_req.readyState == 4) { + try { + + var xml = _req.responseXML; + var txt = _req.responseText.replace(/[\r\n]+/g, ""). + replace(/^(.+)<\?xml.*$/, "$1"); + + //Test case was skipped + var skipElements = xml.getElementsByTagName('skip'); + if (!skipElements || 1 != skipElements.length) + { + var runElements = xml.getElementsByTagName('run'); + //Invalid document, an error probably occured + if (!runElements || 1 != runElements.length) { + reporter.reportException( + "Invalid XML response: " + + _stripTags(txt.replace(/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/g, "")), testClass); + } else { + var everything = runElements.item(0); + _parseResults(everything, testClass); + reporter.finish(); + } + } + else + { + reporter.reportSkip(_textValueOf(skipElements.item(0)), testClass); + reporter.finish(); + } + } catch (ex) { + //Invalid document or an error occurred. + reporter.reportException( + "Invalid XML response: " + + _stripTags(txt.replace(/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/g, "")), testClass); + } + + //Invoke the callback + _this.oncompletion(); + } + } + + /** + * Cross browser method for reading the value of a node in XML. + * @param {Element} node + * @returns String + */ + var _textValueOf = function _textValueOf(node) { + if (!node.textContent && node.text) { + return node.text; + } else { + return node.textContent; + } + } + + var _stripTags = function _stripTags(txt) { + txt = txt.replace(/[\r\n]+/g, ""); + return txt.replace( + /<\/?(?:a|b|br|p|strong|u|i|em|span|div|ul|ol|li|table|thead|tbody|th|td|tr)\b.*?\/?>/g, + ""); + } + + /** + * Parse an arbitrary message output. + * @param {Element} node + * @param {String} path + */ + var _parseMessage = function _parseMessage(node, path) { + reporter.reportOutput(_textValueOf(node), path); + } + + /** + * Parse formatted text output (such as a dump()). + * @param {Element} node + * @param {String} path + */ + var _parseFormatted = function _parseFormatted(node, path) { + reporter.reportOutput(_textValueOf(node), path); + } + + /** + * Parse failing test assertion. + * @param {Element} node + * @param {String} path + */ + var _parseFail = function _parseFail(node, path) { + reporter.reportFail(_textValueOf(node), path); + } + + /** + * Parse an Exception. + * @param {Element} node + * @param {String} path + */ + var _parseException = function _parseException(node, path) { + reporter.reportException(_textValueOf(node), path); + } + + /** + * Parse passing test assertion. + * @param {Element} node + * @param {String} path + */ + var _parsePass = function _parsePass(node, path) { + reporter.reportPass(_textValueOf(node), path); + } + + /** + * Parse an entire test case + * @param {Element} node + * @param {String} path + */ + var _parseTestCase = function _parseTestCase(node, path) { + var testMethodNodes = _xpath.getNodes("./test", node); + + for (var x in testMethodNodes) { + var testMethodNode = testMethodNodes[x]; + var testMethodName = _xpath.getValue("./name", testMethodNode); + + var formattedNodes = _xpath.getNodes("./formatted", testMethodNode); + for (var i in formattedNodes) { + var formattedNode = formattedNodes[i]; + _parseFormatted(formattedNode, path + " -> " + testMethodName); + } + + var messageNodes = _xpath.getNodes("./message", testMethodNode); + for (var i in messageNodes) { + var messageNode = messageNodes[i]; + _parseMessage(messageNode, path + " -> " + testMethodName); + } + + var failNodes = _xpath.getNodes("./fail", testMethodNode); + for (var i in failNodes) { + var failNode = failNodes[i]; + _parseFail(failNode, path + " -> " + testMethodName); + } + + var exceptionNodes = _xpath.getNodes("./exception", testMethodNode); + for (var i in exceptionNodes) { + var exceptionNode = exceptionNodes[i]; + _parseException(exceptionNode, path + " -> " + testMethodName); + } + + var passNodes = _xpath.getNodes("./pass", testMethodNode); + for (var i in passNodes) { + var passNode = passNodes[i]; + _parsePass(passNode, path + " -> " + testMethodName); + } + } + } + + /** + * Parse an entire grouped or single test case. + * @param {Element} node + * @param {String} path + */ + var _parseResults = function _parseResults(node, path) { + var groupNodes = _xpath.getNodes("./group", node); + + if (0 != groupNodes.length) { + for (var i in groupNodes) { + var groupNode = groupNodes[i]; + var groupName = _xpath.getValue("./name", groupNode); + _parseResults(groupNode, path + " -> " + groupName); + } + } else { + var caseNodes = _xpath.getNodes("./case", node); + for (var i in caseNodes) { + var caseNode = caseNodes[i]; + _parseTestCase(caseNode, path); + } + } + } + +} + +/** + * Runs a list of test cases. + * @author Chris Corbyn + * @constructor + */ +function SweetyTestRunner() { + + var _this = this; + + SweetyTestRunner._currentInstance = _this; + + /** True if the test runner has been stopped */ + var _cancelled = false; + + /** + * Invoked to cause the test runner to stop execution at the next available + * opportunity. If XML is being parsed in another thread, or an AJAX request + * is in progress the test runner will wait until the next test. + * @param {Boolean} cancel + */ + this.cancelTesting = function cancelTesting(cancel) { + _cancelled = cancel; + } + + /** + * Run the given list of test cases. + * @param {String[]} tests + * @param {SweetyReporter} reporter + */ + this.runTests = function runTests(tests, reporter) { + if (!reporter.isStarted()) { + reporter.start(); + } + + if (_cancelled || !tests || !tests.length) { + _cancelled = false; + reporter.finish(); + return; + } + + var testCase = tests.shift(); + + var caseReporter = reporter.getReporterFor(testCase); + + var testRun = new SweetyTestCaseRun(testCase, caseReporter); + + //Repeat until no tests remaining in list + // Ok, I know, I know I'll try to eradicate this lazy use of recursion + testRun.oncompletion = function() { + _this.runTests(tests, reporter); + }; + + testRun.run(); + } + +} + +/** Active instance */ +SweetyTestRunner._currentInstance = null; + +/** + * Fetches the currently running instance of the TestRunner. + * @returns SweetyTestRunner + */ +SweetyTestRunner.getCurrentInstance = function getCurrentInstance() { + return this._currentInstance; +} diff --git a/lib/Swift/test-suite/templates/sweety/css/main.css b/lib/Swift/test-suite/templates/sweety/css/main.css new file mode 100644 index 0000000..3f6e630 --- /dev/null +++ b/lib/Swift/test-suite/templates/sweety/css/main.css @@ -0,0 +1,203 @@ +/** Page structure **/ +/*------------------*/ + +ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input { + margin: 0; padding: 0; +} + +body { + font-size: 0.8em; + font-family: arial,sans-serif; + background: #fff; + padding: 8px; +} + +img { + border: 0; +} + +input { + margin-bottom: 8px; + padding: 4px; + font-size: 16px; + width: 100px; +} + +input.sweety-text { + border: 2px solid #000; + width: 268px; + background-color: #fff; +} + +input.sweety-disabled { + background: #eee; + border: 2px solid #888; +} + +input.sweety-waiting { + background-image: url(../images/loading.gif); + background-position: 98% 50%; + background-repeat: no-repeat; +} + +input.sweety-check { + width: 12px !important; + height: 12px !important; + margin: 1px !important; + padding: 1px !important; +} + +h1 { + margin-bottom: 1em; +} + +div#sweety-page { + width: 100%; +} + +div#sweety-testlist { + float: left; + width: 400px; + background: #aaa; +} + +div#sweety-output { + margin-left: 400px; +} + +div#sweety-results { + padding: 8px; + font-size: 1.4em; +} + +div#sweety-messages { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid #777; +} + +div#sweety-smoke-images { + margin-top: 8px; + padding-top: 8px; +} + +div#sweety-smoke-images img { + width: 150px; + height: 120px; + margin-right: 8px; +} + +div.sweety-clear { + width: 100%; + clear: both; +} + +/** Look and feel **/ +/*-----------------*/ + +input#sweety-filter { + visibility: hidden; +} + +div#sweety-communication { + float: right; + display: none; +} + +pre.sweety-raw-output { + display: block; + background: #ddd; + padding: 4px; + margin: 4px; +} + +div.sweety-pad { + padding: 8px; +} + +div.sweety-message { + padding: 4px; + margin: 4px; +} + +div.sweety-test-path { + padding-left: 2em; + font-style: italic; + color: #777; +} + +div.sweety-test { + font-size: 12px; + margin-bottom: 2px; + margin-left: 12px; + padding: 2px; +} + +div.sweety-test img { + float: right; + margin-right: 4px; +} + +div.sweety-package-header { + font-size: 12px; + margin-bottom: 2px; + border-bottom: 2px solid #444; + padding: 2px; +} + +div.sweety-package-header span.sweety-test-package { + font-weight: normal; +} + +div.sweety-package-header img { + margin: 0 2px; +} + +span.sweety-pkg-count { + display: block; + float: right; + padding-right: 2px; +} + +img.sweety-group-icon { + float: right; + margin: 4px !important; +} + +.sweety-pkg-idle { + background: #cacaca; +} + +span.sweety-test-package { + display: block; + font-size: 10px; + padding-left: 1em; +} + +.sweety-idle { + background: #eee; +} + +.sweety-running { + background: #ffff33 !important; +} + +.sweety-pass { + background: #00aa00 !important; + color: #fff; +} + +.sweety-fail { + background: #ee0000 !important; + color: #fff; +} + +.sweety-fail-text { + color: #ee0000; +} + +.sweety-skip-text { + color: #4444dd; + font-weight: bold; + text-decoration: underline; +} diff --git a/lib/Swift/test-suite/templates/sweety/images/darr.gif b/lib/Swift/test-suite/templates/sweety/images/darr.gif new file mode 100644 index 0000000000000000000000000000000000000000..d4349a76a1ccb4f175f9ecb44661b44f2a7398d1 GIT binary patch literal 57 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 a+oaG_sP?M^QkcF_(t*<#irGR~-|{y@REX37 literal 0 HcmV?d00001 diff --git a/lib/Swift/test-suite/templates/sweety/images/rarr.gif b/lib/Swift/test-suite/templates/sweety/images/rarr.gif new file mode 100644 index 0000000000000000000000000000000000000000..b2d4d95dfd6709375c7dbbd437ff6c52c186f361 GIT binary patch literal 59 zcmZ?wbhEHb " + _pkgs[unescape(nvp[0])]); + } + } + + var _pkgFor = function _pkgFor(testName) { + return testName.replace(/_?[^_]+$/, ""); + } + +} + +//Create an instance of the UI Manager for usage +var sweetyUI = new SweetyUIManager(); + + +/** + * A filter to hide/show test cases in the list. + * @author Chris Corbyn + * @consructor + */ +function SweetyFilter() { + + var _this = this; + + /** Asynchronous page timer (so nothing happens whilst typing) */ + var _timer; + + /** The sweety-filter element, lazy loaded */ + var _filter = null; + + /** + * Update the display once the search is complete. + */ + this.repaintUI = function repaintUI() { + sweetyUI.initialize(); + sweetyUI.paintSearchComplete(); + } + + /** + * Search for matching test cases. + */ + this.search = function search() { + sweetyUI.paintSearching(); + + var query = _getFilterInput().value.toLowerCase(); + var queryBits = query.split(/[^\!a-zA-Z0-9_]+/g); + + //Cancel searching if still typing + try { + window.clearTimeout(_timer); + } catch (e) { } + + for (var testCase in sweetyTestCases) { + for (var i in queryBits) { + var testFor = queryBits[i]; + var isNegated = ("!" == testFor.charAt(0)); + if (isNegated) { + testFor = testFor.substring(1); + } + + if (!isNegated && 0 > testCase.toLowerCase().indexOf(testFor)) { + sweetyTestCases[testCase] = false; + break; + } else if (isNegated && 0 < testCase.toLowerCase().indexOf(testFor)) { + sweetyTestCases[testCase] = false; + break; + } else { + sweetyTestCases[testCase] = true; + } + } + } + + //Only apply the search in 500ms, since user may be typing + _timer = window.setTimeout(_this.repaintUI, 500); + } + + /** + * Get a lazy loaded reference to the input element. + * @return HTMLInputElement + */ + var _getFilterInput = function _getFilterInput() { + if (!_filter) { + _filter = document.getElementById("sweety-filter"); + } + return _filter; + } + +} + +//Create a new instance of the filter +var sweetyFilter = new SweetyFilter(); + +/** + * The reporter which gathers aggregate results and displays a summary. + * @author Chris Corbyn + * @constructor + * @param {Boolean} reportPkgs if package status should be reported + */ +function SweetyTemplateAggregateReporter(testCaseList, reportPkgs) { + + var _this = this; + + /** True if this reporter instance is running now */ + var _started = false; + + /** Aggregate totals */ + var _aggregates = { cases : 0, run: 0, passes : 0, fails : 0, exceptions : 0 }; + + /** Aggregates per-package */ + var _pkgs = { }; + + /** Currently running package */ + var _currentPkg; + + /** + * Creates a reporter for the given testCase. + * @param {String} testCase + * @returns SweetyReporter + */ + this.getReporterFor = function getReporterFor(testCase) { + _aggregates.cases++; + + if (reportPkgs) { + var pkg = _getPkgName(testCase); + sweetyUI.paintPkgRunning(pkg); + + _pkgs[pkg].cases++; + + if (_currentPkg && _currentPkg != pkg) { + _updatePkgStatus(_currentPkg); + } + + _currentPkg = pkg; + } + + sweetyUI.paintNumCases(_aggregates.cases); + + var reporter = new SweetyTemplateCaseReporter(testCase, _this); + return reporter; + } + + /** + * Updates the UI with the new aggregate totals. + */ + this.notifyEnded = function notifyEnded(testCase) { + _aggregates.run++; + + if (reportPkgs) { + var pkg = _getPkgName(testCase); + _pkgs[pkg].run++; + } + + //Update the UI with new totals + sweetyUI.paintNumRun(_aggregates.run); + sweetyUI.paintNumPasses(_aggregates.passes); + sweetyUI.paintNumFails(_aggregates.fails); + sweetyUI.paintNumExceptions(_aggregates.exceptions); + } + + /** + * Returns true if this reporter instance is running. + * @returns Boolean + */ + this.isStarted = function isStarted() { + return _started; + } + + /** + * Start reporting. + */ + this.start = function start() { + _started = true; + + if (reportPkgs) + { + for (var i = 0, len = testCaseList.length; i < len; i++) { + var testCase = testCaseList[i]; + var pkg = _getPkgName(testCase); + if (typeof _pkgs[pkg] == "undefined") { + _pkgs[pkg] = { cases : 0, run : 0, passes : 0, fails : 0, exceptions : 0 }; + } + } + } + + sweetyUI.allowInteractivity(false); + sweetyUI.paintNetworking(true); + sweetyUI.paintAllRunning(); + } + + /** + * Report a skipped test case. + * @param {String} message + * @param {String} path + */ + this.reportSkip = function reportSkip(message, path) { + sweetyUI.paintSkip(message, path); + } + + /** + * Report a passing assertion. + * @param {String} message + * @param {String} path + */ + this.reportPass = function reportPass(message, path) { + _aggregates.passes++; + + if (reportPkgs) { + _pkgs[_currentPkg].passes++; + } + } + + /** + * Report a failing assertion. + * @param {String} message + * @param {String} path + */ + this.reportFail = function reportFail(message, path) { + _aggregates.fails++; + + if (reportPkgs) { + _pkgs[_currentPkg].fails++; + } + + sweetyUI.paintFail(message, path); + } + + /** + * Report an unexpected exception. + * @param {String} message + * @param {String} path + */ + this.reportException = function reportException(message, path) { + _aggregates.exceptions++; + + if (reportPkgs) { + _pkgs[_currentPkg].exceptions++; + } + + sweetyUI.paintException(message, path); + } + + /** + * Handle test case output from something like a dump(). + * @param {String} output + * @param {String} path + */ + this.reportOutput = function reportOutput(output, path) { + sweetyUI.paintOutput(output, path); + } + + /** + * End reporting. + * This method is used to come to a conclusion about the test results in the UI. + */ + this.finish = function finish() { + _started = false; + + if (reportPkgs) { + _updatePkgStatus(_currentPkg); + } + + sweetyUI.allowInteractivity(true); + + sweetyUI.paintNetworking(false); + + if ((!_aggregates.fails && !_aggregates.exceptions) + && (_aggregates.cases == _aggregates.run)) { + sweetyUI.paintConclusionPassed(); + } else { + sweetyUI.paintConclusionFailed(); + } + + var incompleteCount = _aggregates.cases - _aggregates.run; + + //Check if all tests actually got fully parsed (i.e. finished) + if (0 < incompleteCount) { + sweetyUI.paintMessage( + incompleteCount + " test case(s) did not complete." + + " This may be because invalid XML was output during the test run" + + " and/or because an error occured." + + " Incomplete test cases are shown in yellow. Click the HTML link " + + "next to the test for more detail."); + } + } + + var _getPkgName = function _getPkgName(testCase) { + return testCase.replace(/_?[^_]+$/, ""); + } + + var _updatePkgStatus = function _updatePkgStatus(pkg) { + if ((!_pkgs[pkg].fails && !_pkgs[pkg].exceptions) + && (_pkgs[pkg].cases == _pkgs[pkg].run)) { + sweetyUI.paintPkgPassed(pkg); + } else if (_pkgs[pkg].cases == _pkgs[pkg].run) { + sweetyUI.paintPkgFailed(pkg); + } + } + +} +SweetyTemplateAggregateReporter.prototype = new SweetyReporter(); + +/** + * The reporter class per-test case. + * @author Chris Corbyn + * @consructor + */ +function SweetyTemplateCaseReporter(testCase, reporter) { + + var _this = this; + + /** Aggregate totals */ + var _aggregates = { passes : 0, fails : 0, exceptions : 0 }; + + /** The DIV element showing this test case */ + var _testCaseDiv = document.getElementById(testCase); + + /** True only if this reporter is running */ + var _started = false; + + /** + * Stubbed only to return itself. + * @returns SweetyReporter + */ + this.getReporterFor = function getReporterFor(testCase) { + return _this; + } + + /** + * Returns true when the reporter is started. + * @returns Boolean + */ + this.isStarted = function isStarted() { + return _started; + } + + /** + * Start reporting. + */ + this.start = function start() { + _started = true; + sweetyUI.paintTestCaseRunning(testCase); + } + + /** + * Report a skipped test case. + * @param {String} message + * @param {String} path + */ + this.reportSkip = function reportSkip(message, path) { + reporter.reportSkip(message, path); + } + + /** + * Report a passing assertion. + * @param {String} message + * @param {String} path + */ + this.reportPass = function reportPass(message, path) { + _aggregates.passes++; + reporter.reportPass(message, path); + } + + /** + * Report a failing assertion. + * @param {String} message + * @param {String} path + */ + this.reportFail = function reportFail(message, path) { + _aggregates.fails++; + reporter.reportFail(message, path); + } + + /** + * Report an unexpected exception. + * @param {String} message + * @param {String} path + */ + this.reportException = function reportException(message, path) { + _aggregates.exceptions++; + reporter.reportException(message, path); + } + + /** + * Handle output from a test case in the form of something like a dump(). + * @param {String} output + * @param {string} path + */ + this.reportOutput = function reportOutput(output, path) { + reporter.reportOutput(output, path); + } + + /** + * End reporting. + */ + this.finish = function finish() { + _started = false; + + if (!_aggregates.fails && !_aggregates.exceptions) { + sweetyUI.paintTestCasePassed(testCase); + } else { + sweetyUI.paintTestCaseFailed(testCase); + } + + reporter.notifyEnded(testCase); + } + +} +SweetyTemplateCaseReporter.prototype = new SweetyReporter(); + +/** + * Wraps the invokation of SweetyTestRunner. + * @author Chris Corbyn + * @constructor + */ +function SweetyTestWrapper() { + + var _this = this; + + /** + * Run a single test case. + * @param {String} testClass + */ + this.runTestCase = function runTestCase(testClass) { + var testCaseList = new Array(); + testCaseList.push(testClass); + + var reporter = new SweetyTemplateAggregateReporter(testCaseList); + + var runner = new SweetyTestRunner(); + runner.runTests(testCaseList, reporter); + } + + /** + * Run all selected test cases. + */ + this.runAll = function runAll(pkg) { + var pkgRegex; + if (pkg) { + pkgRegex = new RegExp("^" + pkg + "_[^_]+$"); + } + + var testCaseList = new Array(); + + for (var testCase in sweetyTestCases) { + if (!sweetyTestCases[testCase] || (pkg && !testCase.match(pkgRegex))) { + continue; + } + testCaseList.push(testCase); + } + + var reporter = new SweetyTemplateAggregateReporter(testCaseList, true); + + var runner = new SweetyTestRunner(); + runner.runTests(testCaseList, reporter); + } + +} + +//Create an instance of the test runner for usage +var sweetyRunner = new SweetyTestWrapper(); + +if (typeof document.onreadystatechange != "undefined") { //IE 6/7 + document.onreadystatechange = function() { + if (document.readyState == "complete") { + sweetyUI.restore(); sweetyUI.initialize(); + } + }; +} else { //Fallback + window.onload = function() { + sweetyUI.restore(); sweetyUI.initialize(); + }; + + try { //FF + document.addEventListener("DOMContentLoaded", window.onload, false); + } catch (e) { + } +} diff --git a/lib/Swift/test-suite/templates/sweety/suite-ui-noajax.tpl.php b/lib/Swift/test-suite/templates/sweety/suite-ui-noajax.tpl.php new file mode 100644 index 0000000..b9d3ef1 --- /dev/null +++ b/lib/Swift/test-suite/templates/sweety/suite-ui-noajax.tpl.php @@ -0,0 +1,155 @@ + + + + <?php echo $suiteName; ?> - No AJAX + + + +
+ +
+ +
+ +
+ +
+ + +
+ + +
+ + + + + +
+ Not available + Tests + + + +
+ + +
+ +
+ + As XML + As HTML + Run + + + checked="checked" + /> + + + + + + + +
+ +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ +

- No AJAX

+ +
+ / + test cases complete: + passes, + fails and + exceptions. +
+ +
+ +
+ Skip: + +
+ in +
+
+ +
+ Fail: +
+ in +
+
+ +
+ Exception: + +
+ in +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/lib/Swift/test-suite/templates/sweety/suite-ui.tpl.php b/lib/Swift/test-suite/templates/sweety/suite-ui.tpl.php new file mode 100644 index 0000000..90aa84c --- /dev/null +++ b/lib/Swift/test-suite/templates/sweety/suite-ui.tpl.php @@ -0,0 +1,132 @@ + + + + + <?php echo $suiteName; ?> + + + + + + + + +
+ +
+ +
+ +
+ +
+ + +
+ + +
+ + + + + +
+ + Toggle Display + Tests + + + +
+ + +
+ +
+ + As XML + As HTML + Run + + + checked="checked" + /> + + + + + + + +
+ +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ Communicating +
+ +

+ +
+ 0/0 + test cases complete: + 0 passes, + 0 fails and + 0 exceptions. +
+ +
+
+ +
+
+ +
+ +
+ +
+ +
+ + diff --git a/lib/Swift/test-suite/xpath-legacy.js b/lib/Swift/test-suite/xpath-legacy.js new file mode 100644 index 0000000..b864b5d --- /dev/null +++ b/lib/Swift/test-suite/xpath-legacy.js @@ -0,0 +1,2764 @@ +/* JavaScript-XPath 0.1.5 + * (c) 2007 Cybozu Labs, Inc. + * + * JavaScript-XPath is freely distributable under the terms of an MIT-style license. + * For details, see the JavaScript-XPath web site: http://coderepos.org/share/wiki/JavaScript-XPath + * +/*--------------------------------------------------------------------------*/ + +if (!document.implementation + || !document.implementation.hasFeature + || !document.implementation.hasFeature("XPath", null)) (function() { + +var undefined = void(0); + + +var defaultConfig = { + targetFrame: undefined +}; + +var config; + +if (window.jsxpath) { + config = window.jsxpath; +} +else { + var scriptElms = document.getElementsByTagName('script'); + var scriptElm = scriptElms[scriptElms.length - 1]; + var scriptSrc = scriptElm.src; + config = {}; + var scriptSrcMatchResult = scriptSrc.match(/\?(.*)$/); + if (scriptSrcMatchResult) { + var configStrings = scriptSrcMatchResult[1].split('&'); + for (var i = 0, l = configStrings.length; i < l; i ++) { + var configString = configStrings[i]; + var configStringSplited = configString.split('='); + config[configStringSplited[0]] = configStringSplited[1] || true; + } + } +} + +for (var n in defaultConfig) { + if (!(n in config)) config[n] = defaultConfig[n] +} + + +var BinaryExpr; +var FilterExpr; +var FunctionCall; +var Literal; +var NameTest; +var NodeSet; +var NodeType; +var NodeUtil; +var Number; +var PathExpr; +var Step; +var UnaryExpr; +var UnionExpr; +var VariableReference; + +/* + * object: user agent identifier + */ +var uai = new function() { + + var ua = navigator.userAgent; + + if (RegExp == undefined) { + if (ua.indexOf("Opera") >= 0) { + this.opera = true; + } else if (ua.indexOf("Netscape") >= 0) { + this.netscape = true; + } else if (ua.indexOf("Mozilla/") == 0) { + this.mozilla = true; + } else { + this.unknown = slide + } + + if (ua.indexOf("Gecko/") >= 0) { + this.gecko = true; + } + + if (ua.indexOf("Win") >= 0) { + this.windows = true; + } else if (ua.indexOf("Mac") >= 0) { + this.mac = true; + } else if (ua.indexOf("Linux") >= 0) { + this.linux = true; + } else if (ua.indexOf("BSD") >= 0) { + this.bsd = true; + } else if (ua.indexOf("SunOS") >= 0) { + this.sunos = true; + } + } + else { + + /* for Trident/Tasman */ + /*@cc_on + @if (@_jscript) + function jscriptVersion() { + switch (@_jscript_version) { + case 3.0: return "4.0"; + case 5.0: return "5.0"; + case 5.1: return "5.01"; + case 5.5: return "5.5"; + case 5.6: + if ("XMLHttpRequest" in window) return "7.0"; + return "6.0"; + case 5.7: + return "7.0"; + default: return true; + } + } + if (@_win16 || @_win32 || @_win64) { + this.windows = true; + this.trident = jscriptVersion(); + } else if (@_mac || navigator.platform.indexOf("Mac") >= 0) { + // '@_mac' may be 'NaN' even if the platform is Mac, + // so we check 'navigator.platform', too. + this.mac = true; + this.tasman = jscriptVersion(); + } + if (match = ua.match("MSIE ?(\\d+\\.\\d+)b?;")) { + this.ie = match[1]; + this['ie' + match[1].charAt(0)] = true; + } + @else @*/ + + /* for AppleWebKit */ + if (match = ua.match("AppleWebKit/(\\d+(\\.\\d+)*)")) { + this.applewebkit = match[1]; + this['applewebkit' + match[1].charAt(0)] = true; + } + + /* for Gecko */ + else if (typeof(Components) == "object") { + if (match = ua.match("Gecko/(\\d{8})")) { + this.gecko = match[1]; + } else if (navigator.product == "Gecko" + && (match = navigator.productSub.match("^(\\d{8})$"))) { + this.gecko = match[1]; + } + } + + /*@end @*/ + + if (typeof(opera) == "object" && typeof(opera.version) == "function") { + this.opera = opera.version(); + this['opera' + this.opera[0] + this.opera[2]] = true; + + } else if (typeof(opera) == "object" + && (match = ua.match("Opera[/ ](\\d+\\.\\d+)"))) { + this.opera = match[1]; + } else if (this.ie) { + } else if (match = ua.match("Safari/(\\d+(\\.\\d+)*)")) { + this.safari = match[1]; + } else if (match = ua.match("Konqueror/(\\d+(\\.\\d+)*)")) { + this.konqueror = match[1]; + } else if (ua.indexOf("(compatible;") < 0 + && (match = ua.match("^Mozilla/(\\d+\\.\\d+)"))) { + this.mozilla = match[1]; + if (match = ua.match("\\([^(]*rv:(\\d+(\\.\\d+)*).*?\\)")) + this.mozillarv = match[1]; + if (match = ua.match("Firefox/(\\d+(\\.\\d+)*)")) { + this.firefox = match[1]; + } else if (match = ua.match("Netscape\\d?/(\\d+(\\.\\d+)*)")) { + this.netscape = match[1]; + } + } else { + this.unknown = true; + } + + if (ua.indexOf("Win 9x 4.90") >= 0) { + this.windows = "ME"; + } else if (match = ua.match("Win(dows)? ?(NT ?(\\d+\\.\\d+)?|\\d+|XP|ME|Vista)")) { + this.windows = match[2]; + if (match[3]) { + this.winnt = match[3]; + } else switch (match[2]) { + case "2000": this.winnt = "5.0"; break; + case "XP": this.winnt = "5.1"; break; + case "Vista": this.winnt = "6.0"; break; + } + } else if (ua.indexOf("Mac") >= 0) { + this.mac = true; + } else if (ua.indexOf("Linux") >= 0) { + this.linux = true; + } else if (match = ua.match("\\w*BSD")) { + this.bsd = match[0]; + } else if (ua.indexOf("SunOS") >= 0) { + this.sunos = true; + } + } +}; + + +/** + * pseudo class: Lexer + */ +var Lexer = function(source) { + var proto = Lexer.prototype; + var tokens = source.match(proto.regs.token); + for (var i = 0, l = tokens.length; i < l; i ++) { + if (proto.regs.strip.test(tokens[i])) { + tokens.splice(i, 1); + } + } + for (var n in proto) tokens[n] = proto[n]; + tokens.index = 0; + return tokens; +}; + +Lexer.prototype.regs = { + token: /\$?(?:(?![0-9-])[\w-]+:)?(?![0-9-])[\w-]+|\/\/|\.\.|::|\d+(?:\.\d*)?|\.\d+|"[^"]*"|'[^']*'|[!<>]=|(?![0-9-])[\w-]+:\*|\s+|./g, + strip: /^\s/ +}; + +Lexer.prototype.peek = function(i) { + return this[this.index + (i||0)]; +}; +Lexer.prototype.next = function() { + return this[this.index++]; +}; +Lexer.prototype.back = function() { + this.index--; +}; +Lexer.prototype.empty = function() { + return this.length <= this.index; +}; + + +/** + * class: Ctx + */ +var Ctx = function(node, position, last) { + this.node = node; + this.position = position || 1; + this.last = last || 1; +}; + + +/** + * abstract class: BaseExpr + */ +var BaseExpr = function() {}; + +BaseExpr.prototype.number = function(ctx) { + var exrs = this.evaluate(ctx); + if (exrs.isNodeSet) return exrs.number(); + return + exrs; +}; + +BaseExpr.prototype.string = function(ctx) { + var exrs = this.evaluate(ctx); + if (exrs.isNodeSet) return exrs.string(); + return '' + exrs; +}; + +BaseExpr.prototype.bool = function(ctx) { + var exrs = this.evaluate(ctx); + if (exrs.isNodeSet) return exrs.bool(); + return !! exrs; +}; + + +/** + * abstract class: BaseExprHasPredicates + */ +var BaseExprHasPredicates = function() {}; + +BaseExprHasPredicates.parsePredicates = function(lexer, expr) { + while (lexer.peek() == '[') { + lexer.next(); + if (lexer.empty()) { + throw Error('missing predicate expr'); + } + var predicate = BinaryExpr.parse(lexer); + expr.predicate(predicate); + if (lexer.empty()) { + throw Error('unclosed predicate expr'); + } + if (lexer.next() != ']') { + lexer.back(); + throw Error('bad token: ' + lexer.next()); + } + } +}; + +BaseExprHasPredicates.prototyps = new BaseExpr(); + +BaseExprHasPredicates.prototype.evaluatePredicates = function(nodeset, start) { + var predicates, predicate, nodes, node, nodeset, position, reverse; + + reverse = this.reverse; + predicates = this.predicates; + + nodeset.sort(); + + for (var i = start || 0, l0 = predicates.length; i < l0; i ++) { + predicate = predicates[i]; + + var deleteIndexes = []; + var nodes = nodeset.list(); + + for (var j = 0, l1 = nodes.length; j < l1; j ++) { + + position = reverse ? (l1 - j) : (j + 1); + exrs = predicate.evaluate(new Ctx(nodes[j], position, l1)); + + switch (typeof exrs) { + case 'number': + exrs = (position == exrs); + break; + case 'string': + exrs = !!exrs; + break; + case 'object': + exrs = exrs.bool(); + break; + } + + if (!exrs) { + deleteIndexes.push(j); + } + } + + for (var j = deleteIndexes.length - 1, l1 = 0; j >= l1; j --) { + nodeset.del(deleteIndexes[j]); + } + + } + + return nodeset; +}; + + +/** + * class: BinaryExpr + */ +if (!window.BinaryExpr && window.defaultConfig) + window.BinaryExpr = null; + +BinaryExpr = function(op, left, right, datatype) { + this.op = op; + this.left = left; + this.right = right; + + this.datatype = BinaryExpr.ops[op][2]; + + this.needContextPosition = left.needContextPosition || right.needContextPosition; + this.needContextNode = left.needContextNode || right.needContextNode; + + // Optimize [@id="foo"] and [@name="bar"] + if (this.op == '=') { + if (!right.needContextNode && !right.needContextPosition && + right.datatype != 'nodeset' && right.datatype != 'void' && left.quickAttr) { + this.quickAttr = true; + this.attrName = left.attrName; + this.attrValueExpr = right; + } + else if (!left.needContextNode && !left.needContextPosition && + left.datatype != 'nodeset' && left.datatype != 'void' && right.quickAttr) { + this.quickAttr = true; + this.attrName = right.attrName; + this.attrValueExpr = left; + } + } +}; + +BinaryExpr.compare = function(op, comp, left, right, ctx) { + var type, lnodes, rnodes, nodes, nodeset, primitive; + + left = left.evaluate(ctx); + right = right.evaluate(ctx); + + if (left.isNodeSet && right.isNodeSet) { + lnodes = left.list(); + rnodes = right.list(); + for (var i = 0, l0 = lnodes.length; i < l0; i ++) + for (var j = 0, l1 = rnodes.length; j < l1; j ++) + if (comp(NodeUtil.to('string', lnodes[i]), NodeUtil.to('string', rnodes[j]))) + return true; + return false; + } + + if (left.isNodeSet || right.isNodeSet) { + if (left.isNodeSet) + nodeset = left, primitive = right; + else + nodeset = right, primitive = left; + + nodes = nodeset.list(); + type = typeof primitive; + for (var i = 0, l = nodes.length; i < l; i ++) { + if (comp(NodeUtil.to(type, nodes[i]), primitive)) + return true; + } + return false; + } + + if (op == '=' || op == '!=') { + if (typeof left == 'boolean' || typeof right == 'boolean') { + return comp(!!left, !!right); + } + if (typeof left == 'number' || typeof right == 'number') { + return comp(+left, +right); + } + return comp(left, right); + } + + return comp(+left, +right); +}; + + +BinaryExpr.ops = { + 'div': [6, function(left, right, ctx) { + return left.number(ctx) / right.number(ctx); + }, 'number'], + 'mod': [6, function(left, right, ctx) { + return left.number(ctx) % right.number(ctx); + }, 'number'], + '*': [6, function(left, right, ctx) { + return left.number(ctx) * right.number(ctx); + }, 'number'], + '+': [5, function(left, right, ctx) { + return left.number(ctx) + right.number(ctx); + }, 'number'], + '-': [5, function(left, right, ctx) { + return left.number(ctx) - right.number(ctx); + }, 'number'], + '<': [4, function(left, right, ctx) { + return BinaryExpr.compare('<', + function(a, b) { return a < b }, left, right, ctx); + }, 'boolean'], + '>': [4, function(left, right, ctx) { + return BinaryExpr.compare('>', + function(a, b) { return a > b }, left, right, ctx); + }, 'boolean'], + '<=': [4, function(left, right, ctx) { + return BinaryExpr.compare('<=', + function(a, b) { return a <= b }, left, right, ctx); + }, 'boolean'], + '>=': [4, function(left, right, ctx) { + return BinaryExpr.compare('>=', + function(a, b) { return a >= b }, left, right, ctx); + }, 'boolean'], + '=': [3, function(left, right, ctx) { + return BinaryExpr.compare('=', + function(a, b) { return a == b }, left, right, ctx); + }, 'boolean'], + '!=': [3, function(left, right, ctx) { + return BinaryExpr.compare('!=', + function(a, b) { return a != b }, left, right, ctx); + }, 'boolean'], + 'and': [2, function(left, right, ctx) { + return left.bool(ctx) && right.bool(ctx); + }, 'boolean'], + 'or': [1, function(left, right, ctx) { + return left.bool(ctx) || right.bool(ctx); + }, 'boolean'] +}; + + +BinaryExpr.parse = function(lexer) { + var op, precedence, info, expr, stack = [], index = lexer.index; + + while (true) { + + if (lexer.empty()) { + throw Error('missing right expression'); + } + expr = UnaryExpr.parse(lexer); + + op = lexer.next(); + if (!op) { + break; + } + + info = this.ops[op]; + precedence = info && info[0]; + if (!precedence) { + lexer.back(); + break; + } + + while (stack.length && precedence <= this.ops[stack[stack.length-1]][0]) { + expr = new BinaryExpr(stack.pop(), stack.pop(), expr); + } + + stack.push(expr, op); + } + + while (stack.length) { + expr = new BinaryExpr(stack.pop(), stack.pop(), expr); + } + + return expr; +}; + +BinaryExpr.prototype = new BaseExpr(); + +BinaryExpr.prototype.evaluate = function(ctx) { + return BinaryExpr.ops[this.op][1](this.left, this.right, ctx); +}; + +BinaryExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'binary: ' + this.op + '\n'; + indent += ' '; + t += this.left.show(indent); + t += this.right.show(indent); + return t; +}; + + +/** + * class: UnaryExpr + */ +if (!window.UnaryExpr && window.defaultConfig) + window.UnaryExpr = null; + +UnaryExpr = function(op, expr) { + this.op = op; + this.expr = expr; + + this.needContextPosition = expr.needContextPosition; + this.needContextNode = expr.needContextNode; +}; + +UnaryExpr.ops = { '-': 1 }; + +UnaryExpr.parse = function(lexer) { + var token; + if (this.ops[lexer.peek()]) + return new UnaryExpr(lexer.next(), UnaryExpr.parse(lexer)); + else + return UnionExpr.parse(lexer); +}; + +UnaryExpr.prototype = new BaseExpr(); + +UnaryExpr.prototype.datatype = 'number'; + +UnaryExpr.prototype.evaluate = function(ctx) { + return - this.expr.number(ctx); +}; + +UnaryExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'unary: ' + this.op + '\n'; + indent += ' '; + t += this.expr.show(indent); + return t; +}; + + +/** + * class: UnionExpr + */ +if (!window.UnionExpr && window.defaultConfig) + window.UnionExpr = null; + +UnionExpr = function() { + this.paths = []; +}; + +UnionExpr.ops = { '|': 1 }; + + +UnionExpr.parse = function(lexer) { + var union, expr; + + expr = PathExpr.parse(lexer); + if (!this.ops[lexer.peek()]) + return expr; + + union = new UnionExpr(); + union.path(expr); + + while (true) { + if (!this.ops[lexer.next()]) break; + if (lexer.empty()) { + throw Error('missing next union location path'); + } + union.path(PathExpr.parse(lexer)); + } + + + + lexer.back(); + return union; +}; + +UnionExpr.prototype = new BaseExpr(); + +UnionExpr.prototype.datatype = 'nodeset'; + +UnionExpr.prototype.evaluate = function(ctx) { + var paths = this.paths; + var nodeset = new NodeSet(); + for (var i = 0, l = paths.length; i < l; i ++) { + var exrs = paths[i].evaluate(ctx); + if (!exrs.isNodeSet) throw Error('PathExpr must be nodeset'); + nodeset.merge(exrs); + } + return nodeset; +}; + +UnionExpr.prototype.path = function(path) { + this.paths.push(path); + + if (path.needContextPosition) { + this.needContextPosition = true; + } + if (path.needContextNode) { + this.needContextNode = true; + } +} +UnionExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'union:' + '\n'; + indent += ' '; + for (var i = 0; i < this.paths.length; i ++) { + t += this.paths[i].show(indent); + } + return t; +}; + + +/** + * class: PathExpr + */ +if (!window.PathExpr && window.defaultConfig) + window.PathExpr = null; + +PathExpr = function(filter) { + this.filter = filter; + this.steps = []; + + this.datatype = filter.datatype; + + this.needContextPosition = filter.needContextPosition; + this.needContextNode = filter.needContextNode; +}; + +PathExpr.ops = { '//': 1, '/': 1 }; + +PathExpr.parse = function(lexer) { + var op, expr, path, token; + + if (this.ops[lexer.peek()]) { + op = lexer.next(); + token = lexer.peek(); + + if (op == '/' && (lexer.empty() || + (token != '.' && token != '..' && token != '@' && token != '*' && + !token.match(/(?![0-9])[\w]/)))) { + return FilterExpr.root(); + } + + path = new PathExpr(FilterExpr.root()); // RootExpr + + if (lexer.empty()) { + throw Error('missing next location step'); + } + expr = Step.parse(lexer); + path.step(op, expr); + } + else { + expr = FilterExpr.parse(lexer); + if (!expr) { + expr = Step.parse(lexer); + path = new PathExpr(FilterExpr.context()); + path.step('/', expr); + } + else if (!this.ops[lexer.peek()]) + return expr; + else + path = new PathExpr(expr); + } + + while (true) { + if (!this.ops[lexer.peek()]) break; + op = lexer.next(); + if (lexer.empty()) { + throw Error('missing next location step'); + } + path.step(op, Step.parse(lexer)); + } + + return path; +}; + +PathExpr.prototype = new BaseExpr(); + +PathExpr.prototype.evaluate = function(ctx) { + var nodeset = this.filter.evaluate(ctx); + if (!nodeset.isNodeSet) throw Exception('Filter nodeset must be nodeset type'); + + var steps = this.steps; + + for (var i = 0, l0 = steps.length; i < l0 && nodeset.length; i ++) { + var step = steps[i][1]; + var reverse = step.reverse; + var iter = nodeset.iterator(reverse); + var prevNodeset = nodeset; + nodeset = null; + var node, next; + if (!step.needContextPosition && step.axis == 'following') { + for (node = iter(); next = iter(); node = next) { + + // Safari 2 node.contains problem + if (uai.applewebkit4) { + var contains = false; + var ancestor = next; + do { + if (ancestor == node) { + contains = true; + break; + } + } while (ancestor = ancestor.parentNode); + if (!contains) break; + } + else { + try { if (!node.contains(next)) break } + catch(e) { if (!(next.compareDocumentPosition(node) & 8)) break } + } + } + nodeset = step.evaluate(new Ctx(node)); + } + else if (!step.needContextPosition && step.axis == 'preceding') { + node = iter(); + nodeset = step.evaluate(new Ctx(node)); + } + else { + node = iter(); + var j = 0; + nodeset = step.evaluate(new Ctx(node), false, prevNodeset, j); + while (node = iter()) { + j ++; + nodeset.merge(step.evaluate(new Ctx(node), false, prevNodeset, j)); + } + } + } + + return nodeset; +}; + +PathExpr.prototype.step = function(op, step) { + step.op = op; + this.steps.push([op, step]); + + this.quickAttr = false; + + if (this.steps.length == 1) { + if (op == '/' && step.axis == 'attribute') { + var test = step.test; + if (!test.notOnlyElement && test.name != '*') { + this.quickAttr = true; + this.attrName = test.name; + } + } + } +}; + +PathExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'path:' + '\n'; + indent += ' '; + t += indent + 'filter:' + '\n'; + t += this.filter.show(indent + ' '); + if (this.steps.length) { + t += indent + 'steps:' + '\n'; + indent += ' '; + for (var i = 0; i < this.steps.length; i ++) { + var step = this.steps[i]; + t += indent + 'operator: ' + step[0] + '\n'; + t += step[1].show(indent); + } + } + return t; +}; + + +/** + * class: FilterExpr + */ +if (!window.FilterExpr && window.defaultConfig) + window.FilterExpr = null; + +FilterExpr = function(primary) { + this.primary = primary; + this.predicates = []; + + this.datatype = primary.datatype; + + this.needContextPosition = primary.needContextPosition; + + this.needContextNode = primary.needContextNode; +}; + +FilterExpr.parse = function(lexer) { + var expr, filter, token, ch; + + token = lexer.peek(); + ch = token.charAt(0); + + switch (ch) { + case '$': + expr = VariableReference.parse(lexer); + break; + + case '(': + lexer.next(); + expr = BinaryExpr.parse(lexer); + if (lexer.empty()) { + throw Error('unclosed "("'); + } + if (lexer.next() != ')') { + lexer.back(); + throw Error('bad token: ' + lexer.next()); + } + break; + + case '"': + case "'": + expr = Literal.parse(lexer); + break; + + default: + if (!isNaN(+token)) { + expr = Number.parse(lexer); + } + + else if (NodeType.types[token]) { + return null; + } + + else if (ch.match(/(?![0-9])[\w]/) && lexer.peek(1) == '(') { + expr = FunctionCall.parse(lexer); + } + else { + return null; + } + break; + } + + if (lexer.peek() != '[') return expr; + + filter = new FilterExpr(expr); + + BaseExprHasPredicates.parsePredicates(lexer, filter); + + return filter; +}; + +FilterExpr.root = function() { + return new FunctionCall('root-node'); +}; +FilterExpr.context = function() { + return new FunctionCall('context-node'); +}; + +FilterExpr.prototype = new BaseExprHasPredicates(); + +FilterExpr.prototype.evaluate = function(ctx) { + var nodeset = this.primary.evaluate(ctx); + if(!nodeset.isNodeSet) { + if (this.predicates.length) + throw Error( + 'Primary result must be nodeset type ' + + 'if filter have predicate expression'); + return nodeset; + } + + return this.evaluatePredicates(nodeset); +}; + +FilterExpr.prototype.predicate = function(predicate) { + this.predicates.push(predicate); +}; + +FilterExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'filter: ' + '\n'; + indent += ' '; + t += this.primary.show(indent); + if (this.predicates.length) { + t += indent + 'predicates: ' + '\n'; + indent += ' '; + for (var i = 0; i < this.predicates.length; i ++) { + t += this.predicates[i].show(indent); + } + } + return t; +}; + + +if (!window.NodeUtil && window.defaultConfig) + window.NodeUtil = null; + +NodeUtil = { + to: function(valueType, node) { + var type = node.nodeType; +/*@cc_on + if (type == 1 && node.nodeName.toLowerCase() == 'title') { + t = node.text; + } + else +@*/ + if (type == 9 || type == 1) { + if (type == 9) { + node = node.documentElement; + } + else { + node = node.firstChild; + } + for (var t = '', stack = [], i = 0; node;) { + do { + if (node.nodeType != 1) { + t += node.nodeValue; + } +/*@cc_on + else if (node.nodeName.toLowerCase() == 'title') { + t += node.text; + } +@*/ + stack[i++] = node; // push + } while (node = node.firstChild); + while (i && !(node = stack[--i].nextSibling)) {} + } + } + else { + var t = node.nodeValue; + } + switch (valueType) { + case 'number': + return + t; + case 'boolean': + return !! t; + default: + return t; + } + }, + attrPropMap: { + name: 'name', + 'class': 'className', + dir: 'dir', + id: 'id', + name: 'name', + title: 'title' + }, + attrMatch: function(node, attrName, attrValue) { +/*@cc_on @if (@_jscript) + var propName = NodeUtil.attrPropMap[attrName]; + if (!attrName || + attrValue == null && ( + propName && node[propName] || + !propName && node.getAttribute && node.getAttribute(attrName) + ) || + attrValue != null && ( + propName && node[propName] == attrValue || + !propName && node.getAttribute && node.getAttribute(attrName) == attrValue + )) { +@else @*/ + if (!attrName || + attrValue == null && node.hasAttribute && node.hasAttribute(attrName) || + attrValue != null && node.getAttribute && node.getAttribute(attrName) == attrValue) { +/*@end @*/ + return true; + } + else { + return false; + } + }, + getDescendantNodes: function(test, node, nodeset, attrName, attrValue, prevNodeset, prevIndex) { + if (prevNodeset) { + prevNodeset.delDescendant(node, prevIndex); + } +/*@cc_on + if (!test.notOnlyElement || test.type == 8 || (attrName && test.type == 0)) { + + var all = node.all; + if (!all) { + return nodeset; + } + + var name = test.name; + if (test.type == 8) name = '!'; + else if (test.type == 0) name = '*'; + + if (name != '*') { + all = all.tags(name); + if (!all) { + return nodeset; + } + } + + if (attrName) { + var result = [] + var i = 0; + if (attrValue != null && (attrName == 'id' || attrName == 'name')) { + all = all[attrValue]; + if (!all) { + return nodeset; + } + if (!all.length) { + all = [all]; + } + } + + while (node = all[i++]) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) result.push(node); + } + + all = result; + } + + var i = 0; + while (node = all[i++]) { + if (name != '*' || node.tagName != '!') { + nodeset.push(node); + } + } + + return nodeset; + } + + (function (parent) { + var g = arguments.callee; + var node = parent.firstChild; + if (node) { + for (; node; node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + g(node); + } + } + })(node); + + return nodeset; +@*/ + if (attrValue && attrName == 'id' && node.getElementById) { + node = node.getElementById(attrValue); + if (node && test.match(node)) { + nodeset.push(node); + } + } + else if (attrValue && attrName == 'name' && node.getElementsByName) { + var nodes = node.getElementsByName(attrValue); + for (var i = 0, l = nodes.length; i < l; i ++) { + node = nodes[i]; + if (uai.opera ? (node.name == attrValue && test.match(node)) : test.match(node)) { + nodeset.push(node); + } + } + } + else if (attrValue && attrName == 'class' && node.getElementsByClassName) { + var nodes = node.getElementsByClassName(attrValue); + for (var i = 0, l = nodes.length; i < l; i ++) { + node = nodes[i]; + if (node.className == attrValue && test.match(node)) { + nodeset.push(node); + } + } + } + else if (test.notOnlyElement) { + (function (parent) { + var f = arguments.callee; + for (var node = parent.firstChild; node; node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node.nodeType)) nodeset.push(node); + } + f(node); + } + })(node); + } + else { + var name = test.name; + if (node.getElementsByTagName) { + var nodes = node.getElementsByTagName(name); + if (nodes) { + var i = 0; + while (node = nodes[i++]) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) nodeset.push(node); + } + } + } + } + return nodeset; + }, + + getChildNodes: function(test, node, nodeset, attrName, attrValue) { + +/*@cc_on + var children; + + if ((!test.notOnlyElement || test.type == 8 || (attrName && test.type == 0)) && (children = node.children)) { + var name, elm; + + name = test.name; + if (test.type == 8) name = '!'; + else if (test.type == 0) name = '*'; + + if (name != '*') { + children = children.tags(name); + if (!children) { + return nodeset; + } + } + + if (attrName) { + var result = [] + var i = 0; + if (attrName == 'id' || attrName == 'name') { + children = children[attrValue]; + + if (!children) { + return nodeset; + } + + if (!children.length) { + children = [children]; + } + } + + while (node = children[i++]) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) result.push(node); + } + children = result; + } + + var i = 0; + while (node = children[i++]) { + if (name != '*' || node.tagName != '!') { + nodeset.push(node); + } + } + + return nodeset; + } + + for (var i = 0, node = node.firstChild; node; i++, node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + } + + return nodeset; +@*/ + for (var node = node.firstChild; node; node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + } + return nodeset; + } +}; + +/*@cc_on +var AttributeWrapper = function(node, parent, sourceIndex) { + this.node = node; + this.nodeType = 2; + this.nodeValue = node.nodeValue; + this.nodeName = node.nodeName; + this.parentNode = parent; + this.ownerElement = parent; + this.parentSourceIndex = sourceIndex; +}; + +@*/ + + +/** + * class: Step + */ +if (!window.Step && window.defaultConfig) + window.Step = null; + +Step = function(axis, test) { + // TODO check arguments and throw axis error + this.axis = axis; + this.reverse = Step.axises[axis][0]; + this.func = Step.axises[axis][1]; + this.test = test; + this.predicates = []; + this._quickAttr = Step.axises[axis][2] +}; + +Step.axises = { + + ancestor: [true, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + while (node = node.parentNode) { + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex, true); + } + if (test.match(node)) nodeset.unshift(node); + } + return nodeset; + }], + + 'ancestor-or-self': [true, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + do { + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex, true); + } + if (test.match(node)) nodeset.unshift(node); + } while (node = node.parentNode) + return nodeset; + }], + + attribute: [false, function(test, node, nodeset) { + var attrs = node.attributes; + if (attrs) { +/*@cc_on + var sourceIndex = node.sourceIndex; +@*/ + if ((test.notOnlyElement && test.type == 0) || test.name == '*') { + for (var i = 0, l = attrs.length; i < l; i ++) { + var attr = attrs[i]; +/*@cc_on @if (@_jscript) + if (attr.nodeValue) { + nodeset.push(new AttributeWrapper(attr, node, sourceIndex)); + } +@else @*/ + nodeset.push(attr); +/*@end @*/ + } + } + else { + var attr = attrs.getNamedItem(test.name) + +/*@cc_on @if (@_jscript) + if (attr && attr.nodeValue) { + attr = new AttributeWrapper(attr, node, sourceIndex);; +@else @*/ + if (attr) { +/*@end @*/ + nodeset.push(attr); + } + } + } + return nodeset; + }], + + child: [false, NodeUtil.getChildNodes, true], + + descendant: [false, NodeUtil.getDescendantNodes, true], + + 'descendant-or-self': [false, function(test, node, nodeset, attrName, attrValue, prevNodeset, prevIndex) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + return NodeUtil.getDescendantNodes(test, node, nodeset, attrName, attrValue, prevNodeset, prevIndex); + }, true], + + following: [false, function(test, node, nodeset, attrName, attrValue) { + do { + var child = node; + while (child = child.nextSibling) { + if (NodeUtil.attrMatch(child, attrName, attrValue)) { + if (test.match(child)) nodeset.push(child); + } + nodeset = NodeUtil.getDescendantNodes(test, child, nodeset, attrName, attrValue); + } + } while (node = node.parentNode); + return nodeset; + }, true], + + 'following-sibling': [false, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + while (node = node.nextSibling) { + + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex); + } + + if (test.match(node)) { + nodeset.push(node); + } + } + return nodeset; + }], + + namespace: [false, function(test, node, nodeset) { + // not implemented + return nodeset; + }], + + parent: [false, function(test, node, nodeset) { + if (node.nodeType == 9) { + return nodeset; + } + if (node.nodeType == 2) { + nodeset.push(node.ownerElement); + return nodeset; + } + var node = node.parentNode; + if (test.match(node)) nodeset.push(node); + return nodeset; + }], + + preceding: [true, function(test, node, nodeset, attrName, attrValue) { + var parents = []; + do { + parents.unshift(node); + } while (node = node.parentNode); + + for (var i = 1, l0 = parents.length; i < l0; i ++) { + var siblings = []; + node = parents[i]; + while (node = node.previousSibling) { + siblings.unshift(node); + } + + for (var j = 0, l1 = siblings.length; j < l1; j ++) { + node = siblings[j]; + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + nodeset = NodeUtil.getDescendantNodes(test, node, nodeset, attrName, attrValue); + } + } + return nodeset; + }, true], + + 'preceding-sibling': [true, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + while (node = node.previousSibling) { + + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex, true); + } + + if (test.match(node)) { + nodeset.unshift(node) + } + } + return nodeset; + }], + + self: [false, function(test, node, nodeset) { + if (test.match(node)) nodeset.push(node); + return nodeset; + }] +}; + +Step.parse = function(lexer) { + var axis, test, step, token; + + if (lexer.peek() == '.') { + step = this.self(); + lexer.next(); + } + else if (lexer.peek() == '..') { + step = this.parent(); + lexer.next(); + } + else { + if (lexer.peek() == '@') { + axis = 'attribute'; + lexer.next(); + if (lexer.empty()) { + throw Error('missing attribute name'); + } + } + else { + if (lexer.peek(1) == '::') { + + if (!lexer.peek().charAt(0).match(/(?![0-9])[\w]/)) { + throw Error('bad token: ' + lexer.next()); + } + + axis = lexer.next(); + lexer.next(); + + if (!this.axises[axis]) { + throw Error('invalid axis: ' + axis); + } + if (lexer.empty()) { + throw Error('missing node name'); + } + } + else { + axis = 'child'; + } + } + + token = lexer.peek(); + if (!token.charAt(0).match(/(?![0-9])[\w]/)) { + if (token == '*') { + test = NameTest.parse(lexer) + } + else { + throw Error('bad token: ' + lexer.next()); + } + } + else { + if (lexer.peek(1) == '(') { + if (!NodeType.types[token]) { + throw Error('invalid node type: ' + token); + } + test = NodeType.parse(lexer) + } + else { + test = NameTest.parse(lexer); + } + } + step = new Step(axis, test); + } + + BaseExprHasPredicates.parsePredicates(lexer, step); + + return step; +}; + +Step.self = function() { + return new Step('self', new NodeType('node')); +}; + +Step.parent = function() { + return new Step('parent', new NodeType('node')); +}; + +Step.prototype = new BaseExprHasPredicates(); + +Step.prototype.evaluate = function(ctx, special, prevNodeset, prevIndex) { + var node = ctx.node; + var reverse = false; + + if (!special && this.op == '//') { + + if (!this.needContextPosition && this.axis == 'child') { + if (this.quickAttr) { + var attrValue = this.attrValueExpr ? this.attrValueExpr.string(ctx) : null; + var nodeset = NodeUtil.getDescendantNodes(this.test, node, new NodeSet(), this.attrName, attrValue, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset, 1); + } + else { + var nodeset = NodeUtil.getDescendantNodes(this.test, node, new NodeSet(), null, null, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset); + } + } + else { + var step = new Step('descendant-or-self', new NodeType('node')); + var nodes = step.evaluate(ctx, false, prevNodeset, prevIndex).list(); + var nodeset = null; + step.op = '/'; + for (var i = 0, l = nodes.length; i < l; i ++) { + if (!nodeset) { + nodeset = this.evaluate(new Ctx(nodes[i]), true); + } + else { + nodeset.merge(this.evaluate(new Ctx(nodes[i]), true)); + } + } + nodeset = nodeset || new NodeSet(); + } + } + else { + + if (this.needContextPosition) { + prevNodeset = null; + prevIndex = null; + } + + if (this.quickAttr) { + var attrValue = this.attrValueExpr ? this.attrValueExpr.string(ctx) : null; + var nodeset = this.func(this.test, node, new NodeSet(), this.attrName, attrValue, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset, 1); + } + else { + var nodeset = this.func(this.test, node, new NodeSet(), null, null, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset); + } + if (prevNodeset) { + prevNodeset.doDel(); + } + } + return nodeset; +}; + +Step.prototype.predicate = function(predicate) { + this.predicates.push(predicate); + + if (predicate.needContextPosition || + predicate.datatype == 'number'|| + predicate.datatype == 'void') { + this.needContextPosition = true; + } + + if (this._quickAttr && this.predicates.length == 1 && predicate.quickAttr) { + var attrName = predicate.attrName; +/*@cc_on @if (@_jscript) + this.attrName = attrName.toLowerCase(); +@else @*/ + this.attrName = attrName; +/*@end @*/ + this.attrValueExpr = predicate.attrValueExpr; + this.quickAttr = true; + } +}; + +Step.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'step: ' + '\n'; + indent += ' '; + if (this.axis) t += indent + 'axis: ' + this.axis + '\n'; + t += this.test.show(indent); + if (this.predicates.length) { + t += indent + 'predicates: ' + '\n'; + indent += ' '; + for (var i = 0; i < this.predicates.length; i ++) { + t += this.predicates[i].show(indent); + } + } + return t; +}; + + + +/** + * NodeType + */ +if (!window.NodeType && window.defaultConfig) + window.NodeType = null; + +NodeType = function(name, literal) { + this.name = name; + this.literal = literal; + + switch (name) { + case 'comment': + this.type = 8; + break; + case 'text': + this.type = 3; + break; + case 'processing-instruction': + this.type = 7; + break; + case 'node': + this.type = 0; + break; + } +}; + +NodeType.types = { + 'comment':1, 'text':1, 'processing-instruction':1, 'node':1 +}; + +NodeType.parse = function(lexer) { + var type, literal, ch; + type = lexer.next(); + lexer.next(); + if (lexer.empty()) { + throw Error('bad nodetype'); + } + ch = lexer.peek().charAt(0); + if (ch == '"' || ch == "'") { + literal = Literal.parse(lexer); + } + if (lexer.empty()) { + throw Error('bad nodetype'); + } + if (lexer.next() != ')') { + lexer.back(); + throw Error('bad token ' + lexer.next()); + } + return new NodeType(type, literal); +}; + +NodeType.prototype = new BaseExpr(); + +NodeType.prototype.notOnlyElement = true; + +NodeType.prototype.match = function(node) { + return !this.type || this.type == node.nodeType; +}; + +NodeType.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'nodetype: ' + this.type + '\n'; + if (this.literal) { + indent += ' '; + t += this.literal.show(indent); + } + return t; +}; + + +/** + * NodeType + */ +if (!window.NameTest && window.defaultConfig) + window.NameTest = null; + +NameTest = function(name) { + this.name = name.toLowerCase(); +}; + +NameTest.parse = function(lexer) { + if (lexer.peek() != '*' && lexer.peek(1) == ':' && lexer.peek(2) == '*') { + return new NameTest(lexer.next() + lexer.next() + lexer.next()); + } + return new NameTest(lexer.next()); +}; + +NameTest.prototype = new BaseExpr(); + +NameTest.prototype.match = function(node) { + var type = node.nodeType; + + if (type == 1 || type == 2) { + if (this.name == '*' || this.name == node.nodeName.toLowerCase()) { + return true; + } + } + return false; +}; + +NameTest.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'nametest: ' + this.name + '\n'; + return t; +}; + + +/** + * class: VariableRefernce + */ +if (!window.VariableReference && window.defaultConfig) + window.VariableReference = null; + +VariableReference = function(name) { + this.name = name.substring(1); +}; + + +VariableReference.parse = function(lexer) { + var token = lexer.next(); + if (token.length < 2) { + throw Error('unnamed variable reference'); + } + return new VariableReference(token) +}; + +VariableReference.prototype = new BaseExpr(); + +VariableReference.prototype.datatype = 'void'; + +VariableReference.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'variable: ' + this.name + '\n'; + return t; +}; + + +/** + * class: Literal + */ +if (!window.Literal && window.defaultConfig) + window.Literal = null; + +Literal = function(text) { + this.text = text.substring(1, text.length - 1); +}; + +Literal.parse = function(lexer) { + var token = lexer.next(); + if (token.length < 2) { + throw Error('unclosed literal string'); + } + return new Literal(token) +}; + +Literal.prototype = new BaseExpr(); + +Literal.prototype.datatype = 'string'; + +Literal.prototype.evaluate = function(ctx) { + return this.text; +}; + +Literal.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'literal: ' + this.text + '\n'; + return t; +}; + + +/** + * class: Number + */ +if (!window.Number && window.defaultConfig) + window.Number = null; + +Number = function(digit) { + this.digit = +digit; +}; + + +Number.parse = function(lexer) { + return new Number(lexer.next()); +}; + +Number.prototype = new BaseExpr(); + +Number.prototype.datatype = 'number'; + +Number.prototype.evaluate = function(ctx) { + return this.digit; +}; + +Number.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'number: ' + this.digit + '\n'; + return t; +}; + + +/** + * class: FunctionCall + */ +if (!window.FunctionCall && window.defaultConfig) + window.FunctionCall = null; + +FunctionCall = function(name) { + var info = FunctionCall.funcs[name]; + this.name = name; + this.func = info[0]; + this.args = []; + + this.datatype = info[1]; + + if (info[2]) { + this.needContextPosition = true; + } + + this.needContextNodeInfo = info[3]; + this.needContextNode = this.needContextNodeInfo[0] +}; + +FunctionCall.funcs = { + + // Original Function + 'context-node': [function() { + if (arguments.length != 0) { + throw Error('Function context-node expects ()'); + } + var ns; + ns = new NodeSet(); + ns.push(this.node); + return ns; + }, 'nodeset', false, [true]], + + // Original Function + 'root-node': [function() { + if (arguments.length != 0) { + throw Error('Function root-node expects ()'); + } + var ns, ctxn; + ns = new NodeSet(); + ctxn = this.node; + if (ctxn.nodeType == 9) + ns.push(ctxn); + else + ns.push(ctxn.ownerDocument); + return ns; + }, 'nodeset', false, []], + + last: [function() { + if (arguments.length != 0) { + throw Error('Function last expects ()'); + } + return this.last; + }, 'number', true, []], + + position: [function() { + if (arguments.length != 0) { + throw Error('Function position expects ()'); + } + return this.position; + }, 'number', true, []], + + count: [function(ns) { + if (arguments.length != 1 || !(ns = ns.evaluate(this)).isNodeSet) { + throw Error('Function count expects (nodeset)'); + } + return ns.length; + }, 'number', false, []], + + id: [function(s) { + var ids, ns, i, id, elm, ctxn, doc; + if (arguments.length != 1) { + throw Error('Function id expects (object)'); + } + ctxn = this.node; + if (ctxn.nodeType == 9) + doc = ctxn; + else + doc = ctxn.ownerDocument; +/*@cc_on + all = doc.all; +@*/ + s = s.string(this); + ids = s.split(/\s+/); + ns = new NodeSet(); + for (i = 0, l = ids.length; i < l; i ++) { + id = ids[i]; + +/*@cc_on @if (@_jscript) + elm = all[id]; + if (elm) { + if (elm.length) { + var elms = elm; + for (var j = 0, l0 = elms.length; j < l0; j ++) { + var elem = elms[j]; + if (id == elem.id) { + ns.push(elem); + break; + } + } + } + else if (id == elm.id) { + ns.push(elm) + } + } +@else @*/ + elm = doc.getElementById(id); + if (uai.opera && elm.id != id) { + var elms = doc.getElementsByName(id); + for (var j = 0, l0 = elms.length; j < l0; j ++) { + elm = elms[j]; + if (elm.id == id) { + ns.push(elm); + } + } + } + else { + if (elm) ns.push(elm) + } +/*@end @*/ + + } + ns.isSorted = false; + return ns; + }, 'nodeset', false, []], + + 'local-name': [function(ns) { + var nd; + switch (arguments.length) { + case 0: + nd = this.node; + break; + case 1: + if ((ns = ns.evaluate(this)).isNodeSet) { + nd = ns.first(); + break; + } + default: + throw Error('Function local-name expects (nodeset?)'); + break; + } + return '' + nd.nodeName.toLowerCase(); + }, 'string', false, [true, false]], + + name: [function(ns) { + // not implemented + return FunctionCall.funcs['local-name'][0].apply(this, arguments); + }, 'string', false, [true, false]], + + 'namespace-uri': [function(ns) { + // not implemented + return ''; + }, 'string', false, [true, false]], + + string: [function(s) { + switch (arguments.length) { + case 0: + s = NodeUtil.to('string', this.node); + break; + case 1: + s = s.string(this); + break; + default: + throw Error('Function string expects (object?)'); + break; + } + return s; + }, 'string', false, [true, false]], + + concat: [function(s1, s2) { + if (arguments.length < 2) { + throw Error('Function concat expects (string, string[, ...])'); + } + for (var t = '', i = 0, l = arguments.length; i < l; i ++) { + t += arguments[i].string(this); + } + return t; + }, 'string', false, []], + + 'starts-with': [function(s1, s2) { + if (arguments.length != 2) { + throw Error('Function starts-with expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + return s1.indexOf(s2) == 0; + }, 'boolean', false, []], + + contains: [function(s1, s2) { + if (arguments.length != 2) { + throw Error('Function contains expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + return s1.indexOf(s2) != -1; + }, 'boolean', false, []], + + substring: [function(s, n1, n2) { + var a1, a2; + s = s.string(this); + n1 = n1.number(this); + switch (arguments.length) { + case 2: + n2 = s.length - n1 + 1; + break; + case 3: + n2 = n2.number(this); + break; + default: + throw Error('Function substring expects (string, string)'); + break; + } + n1 = Math.round(n1); + n2 = Math.round(n2); + a1 = n1 - 1; + a2 = n1 + n2 - 1; + if (a2 == Infinity) { + return s.substring(a1 < 0 ? 0 : a1); + } + else { + return s.substring(a1 < 0 ? 0 : a1, a2) + } + }, 'string', false, []], + + 'substring-before': [function(s1, s2) { + var n; + if (arguments.length != 2) { + throw Error('Function substring-before expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + n = s1.indexOf(s2); + if (n == -1) return ''; + return s1.substring(0, n); + }, 'string', false, []], + + 'substring-after': [function(s1, s2) { + if (arguments.length != 2) { + throw Error('Function substring-after expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + var n = s1.indexOf(s2); + if (n == -1) return ''; + return s1.substring(n + s2.length); + }, 'string', false, []], + + 'string-length': [function(s) { + switch (arguments.length) { + case 0: + s = NodeUtil.to('string', this.node); + break; + case 1: + s = s.string(this); + break; + default: + throw Error('Function string-length expects (string?)'); + break; + } + return s.length; + }, 'number', false, [true, false]], + + 'normalize-space': [function(s) { + switch (arguments.length) { + case 0: + s = NodeUtil.to('string', this.node); + break; + case 1: + s = s.string(this); + break; + default: + throw Error('Function normalize-space expects (string?)'); + break; + } + return s.replace(/\s+/g, ' ').replace(/^ /, '').replace(/ $/, ''); + }, 'string', false, [true, false]], + + translate: [function(s1, s2, s3) { + if (arguments.length != 3) { + throw Error('Function translate expects (string, string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + s3 = s3.string(this); + + var map = []; + for (var i = 0, l = s2.length; i < l; i ++) { + var ch = s2.charAt(i); + if (!map[ch]) map[ch] = s3.charAt(i) || ''; + } + for (var t = '', i = 0, l = s1.length; i < l; i ++) { + var ch = s1.charAt(i); + var replace = map[ch] + t += (replace != undefined) ? replace : ch; + } + return t; + }, 'string', false, []], + + 'boolean': [function(b) { + if (arguments.length != 1) { + throw Error('Function boolean expects (object)'); + } + return b.bool(this) + }, 'boolean', false, []], + + not: [function(b) { + if (arguments.length != 1) { + throw Error('Function not expects (object)'); + } + return !b.bool(this) + }, 'boolean', false, []], + + 'true': [function() { + if (arguments.length != 0) { + throw Error('Function true expects ()'); + } + return true; + }, 'boolean', false, []], + + 'false': [function() { + if (arguments.length != 0) { + throw Error('Function false expects ()'); + } + return false; + }, 'boolean', false, []], + + lang: [function(s) { + // not implemented + return false; + }, 'boolean', false, []], + + number: [function(n) { + switch (arguments.length) { + case 0: + n = NodeUtil.to('number', this.node); + break; + case 1: + n = n.number(this); + break; + default: + throw Error('Function number expects (object?)'); + break; + } + return n; + }, 'number', false, [true, false]], + + sum: [function(ns) { + var nodes, n, i, l; + if (arguments.length != 1 || !(ns = ns.evaluate(this)).isNodeSet) { + throw Error('Function sum expects (nodeset)'); + } + nodes = ns.list(); + n = 0; + for (i = 0, l = nodes.length; i < l; i ++) { + n += NodeUtil.to('number', nodes[i]); + } + return n; + }, 'number', false, []], + + floor: [function(n) { + if (arguments.length != 1) { + throw Error('Function floor expects (number)'); + } + n = n.number(this); + return Math.floor(n); + }, 'number', false, []], + + ceiling: [function(n) { + if (arguments.length != 1) { + throw Error('Function ceiling expects (number)'); + } + n = n.number(this); + return Math.ceil(n); + }, 'number', false, []], + + round: [function(n) { + if (arguments.length != 1) { + throw Error('Function round expects (number)'); + } + n = n.number(this); + return Math.round(n); + }, 'number', false, []] +}; + +FunctionCall.parse = function(lexer) { + var expr, func = new FunctionCall(lexer.next()); + lexer.next(); + while (lexer.peek() != ')') { + if (lexer.empty()) { + throw Error('missing function argument list'); + } + expr = BinaryExpr.parse(lexer); + func.arg(expr); + if (lexer.peek() != ',') break; + lexer.next(); + } + if (lexer.empty()) { + throw Error('unclosed function argument list'); + } + if (lexer.next() != ')') { + lexer.back(); + throw Error('bad token: ' + lexer.next()); + } + return func +}; + +FunctionCall.prototype = new BaseExpr(); + +FunctionCall.prototype.evaluate = function (ctx) { + return this.func.apply(ctx, this.args); +}; + +FunctionCall.prototype.arg = function(arg) { + this.args.push(arg); + + if (arg.needContextPosition) { + this.needContextPosition = true; + } + + var args = this.args; + if (arg.needContextNode) { + args.needContexNode = true; + } + this.needContextNode = args.needContextNode || + this.needContextNodeInfo[args.length]; +}; + +FunctionCall.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'function: ' + this.name + '\n'; + indent += ' '; + + if (this.args.length) { + t += indent + 'arguments: ' + '\n'; + indent += ' '; + for (var i = 0; i < this.args.length; i ++) { + t += this.args[i].show(indent); + } + } + + return t; +}; + + +/*@cc_on @if (@_jscript) +var NodeWrapper = function(node, sourceIndex, subIndex, attributeName) { + this.node = node; + this.nodeType = node.nodeType; + this.sourceIndex = sourceIndex; + this.subIndex = subIndex; + this.attributeName = attributeName || ''; + this.order = String.fromCharCode(sourceIndex) + String.fromCharCode(subIndex) + attributeName; +}; + +NodeWrapper.prototype.toString = function() { + return this.order; +}; +@else @*/ +var NodeID = { + uuid: 1, + get: function(node) { + return node.__jsxpath_id__ || (node.__jsxpath_id__ = this.uuid++); + } +}; +/*@end @*/ + +if (!window.NodeSet && window.defaultConfig) + window.NodeSet = null; + +NodeSet = function() { + this.length = 0; + this.nodes = []; + this.seen = {}; + this.idIndexMap = null; + this.reserveDels = []; +}; + +NodeSet.prototype.isNodeSet = true; +NodeSet.prototype.isSorted = true; + +/*@_cc_on +NodeSet.prototype.shortcut = true; +@*/ + +NodeSet.prototype.merge = function(nodeset) { + this.isSorted = false; + if (nodeset.only) { + return this.push(nodeset.only); + } + + if (this.only){ + var only = this.only; + delete this.only; + this.push(only); + this.length --; + } + + var nodes = nodeset.nodes; + for (var i = 0, l = nodes.length; i < l; i ++) { + this._add(nodes[i]); + } +}; + +NodeSet.prototype.sort = function() { + if (this.only) return; + if (this.sortOff) return; + + if (!this.isSorted) { + this.isSorted = true; + this.idIndexMap = null; + +/*@cc_on + if (this.shortcut) { + this.nodes.sort(); + } + else { + this.nodes.sort(function(a, b) { + var result; + result = a.sourceIndex - b.sourceIndex; + if (result == 0) + return a.subIndex - a.subIndex; + else + return result; + }); + } + return; +@*/ + var nodes = this.nodes; + nodes.sort(function(a, b) { + if (a == b) return 0; + + if (a.compareDocumentPosition) { + var result = a.compareDocumentPosition(b); + if (result & 2) return 1; + if (result & 4) return -1; + return 0; + } + else { + var node1 = a, node2 = b, ancestor1 = a, ancestor2 = b, deep1 = 0, deep2 = 0; + + while(ancestor1 = ancestor1.parentNode) deep1 ++; + while(ancestor2 = ancestor2.parentNode) deep2 ++; + + // same deep + if (deep1 > deep2) { + while (deep1-- != deep2) node1 = node1.parentNode; + if (node1 == node2) return 1; + } + else if (deep2 > deep1) { + while (deep2-- != deep1) node2 = node2.parentNode; + if (node1 == node2) return -1; + } + + while ((ancestor1 = node1.parentNode) != (ancestor2 = node2.parentNode)) { + node1 = ancestor1; + node2 = ancestor2; + } + + // node1 is node2's sibling + while (node1 = node1.nextSibling) if (node1 == node2) return -1; + + return 1; + } + }); + } +}; + + +/*@cc_on @if (@_jscript) +NodeSet.prototype.sourceOffset = 1; +NodeSet.prototype.subOffset = 2; +NodeSet.prototype.createWrapper = function(node) { + var parent, child, attributes, attributesLength, sourceIndex, subIndex, attributeName; + + sourceIndex = node.sourceIndex; + + if (typeof sourceIndex != 'number') { + type = node.nodeType; + switch (type) { + case 2: + parent = node.parentNode; + sourceIndex = node.parentSourceIndex; + subIndex = -1; + attributeName = node.nodeName; + break; + case 9: + subIndex = -2; + sourceIndex = -1; + break; + default: + child = node; + subIndex = 0; + do { + subIndex ++; + sourceIndex = child.sourceIndex; + if (sourceIndex) { + parent = child; + child = child.lastChild; + if (!child) { + child = parent; + break; + } + subIndex ++; + } + } while (child = child.previousSibling); + if (!sourceIndex) { + sourceIndex = node.parentNode.sourceIndex; + } + break; + } + } + else { + subIndex = -2; + } + + sourceIndex += this.sourceOffset; + subIndex += this.subOffset; + + return new NodeWrapper(node, sourceIndex, subIndex, attributeName); +}; + +NodeSet.prototype.reserveDelBySourceIndexAndSubIndex = function(sourceIndex, subIndex, offset, reverse) { + var map = this.createIdIndexMap(); + var index; + if ((map = map[sourceIndex]) && (index = map[subIndex])) { + if (reverse && (this.length - offset - 1) > index || !reverse && offset < index) { + var obj = { + value: index, + order: String.fromCharCode(index), + toString: function() { return this.order }, + valueOf: function() { return this.value } + }; + this.reserveDels.push(obj); + } + } +}; +@else @*/ +NodeSet.prototype.reserveDelByNodeID = function(id, offset, reverse) { + var map = this.createIdIndexMap(); + var index; + if (index = map[id]) { + if (reverse && (this.length - offset - 1) > index || !reverse && offset < index) { + var obj = { + value: index, + order: String.fromCharCode(index), + toString: function() { return this.order }, + valueOf: function() { return this.value } + }; + this.reserveDels.push(obj); + } + } +}; +/*@end @*/ + +NodeSet.prototype.reserveDelByNode = function(node, offset, reverse) { +/*@cc_on @if (@_jscript) + node = this.createWrapper(node); + this.reserveDelBySourceIndexAndSubIndex(node.sourceIndex, node.subIndex, offset, reverse); +@else @*/ + this.reserveDelByNodeID(NodeID.get(node), offset, reverse); +/*@end @*/ +}; + +NodeSet.prototype.doDel = function() { + if (!this.reserveDels.length) return; + + if (this.length < 0x10000) { + var dels = this.reserveDels.sort(function(a, b) { return b - a }); + } + else { + var dels = this.reserveDels.sort(function(a, b) { return b - a }); + } + for (var i = 0, l = dels.length; i < l; i ++) { + this.del(dels[i]); + } + this.reserveDels = []; + this.idIndexMap = null; +}; + +NodeSet.prototype.createIdIndexMap = function() { + if (this.idIndexMap) { + return this.idIndexMap; + } + else { + var map = this.idIndexMap = {}; + var nodes = this.nodes; + for (var i = 0, l = nodes.length; i < l; i ++) { + var node = nodes[i]; +/*@cc_on @if (@_jscript) + var sourceIndex = node.sourceIndex; + var subIndex = node.subIndex; + if (!map[sourceIndex]) map[sourceIndex] = {}; + map[sourceIndex][subIndex] = i; +@else @*/ + var id = NodeID.get(node); + map[id] = i; +/*@end @*/ + } + return map; + } +}; + +NodeSet.prototype.del = function(index) { + this.length --; + if (this.only) { + delete this.only; + } + else { + var node = this.nodes.splice(index, 1)[0]; + + if (this._first == node) { + delete this._first; + delete this._firstSourceIndex; + delete this._firstSubIndex; + } + +/*@cc_on @if (@_jscript) + delete this.seen[node.sourceIndex][node.subIndex]; +@else @*/ + delete this.seen[NodeID.get(node)]; +/*@end @*/ + } +}; + + +NodeSet.prototype.delDescendant = function(elm, offset) { + if (this.only) return; + var nodeType = elm.nodeType; + if (nodeType != 1 && nodeType != 9) return; + if (uai.applewebkit4) return; + + // element || document + if (!elm.contains) { + if (nodeType == 1) { + var _elm = elm; + elm = { + contains: function(node) { + return node.compareDocumentPosition(_elm) & 8; + } + }; + } + else { + // document + elm = { + contains: function() { + return true; + } + }; + } + } + + var nodes = this.nodes; + for (var i = offset + 1; i < nodes.length; i ++) { + +/*@cc_on @if (@_jscript) + if (nodes[i].node.nodeType == 1 && elm.contains(nodes[i].node)) { +@else @*/ + if (elm.contains(nodes[i])) { +/*@end @*/ + this.del(i); + i --; + } + } +}; + +NodeSet.prototype._add = function(node, reverse) { + +/*@cc_on @if (@_jscript) + + var first, firstSourceIndex, firstSubIndex, sourceIndex, subIndex, attributeName; + + sourceIndex = node.sourceIndex; + subIndex = node.subIndex; + attributeName = node.attributeName; + seen = this.seen; + + seen = seen[sourceIndex] || (seen[sourceIndex] = {}); + + if (node.nodeType == 2) { + seen = seen[subIndex] || (seen[subIndex] = {}); + if (seen[attributeName]) { + return true; + } + seen[attributeName] = true; + } + else { + if (seen[subIndex]) { + return true; + } + seen[subIndex] = true; + } + + if (sourceIndex >= 0x10000 || subIndex >= 0x10000) { + this.shortcut = false; + } + + // if this._first is undefined and this.nodes is not empty + // then first node shortcut is disabled. + if (this._first || this.nodes.length == 0) { + first = this._first; + firstSourceIndex = this._firstSourceIndex; + firstSubIndex = this._firstSubIndex; + if (!first || firstSourceIndex > sourceIndex || (firstSourceIndex == sourceIndex && firstSubIndex > subIndex)) { + this._first = node; + this._firstSourceIndex = sourceIndex; + this._firstSubIndex = subIndex + } + } + +@else @*/ + + var seen = this.seen; + var id = NodeID.get(node); + if (seen[id]) return true; + seen[id] = true; + +/*@end @*/ + + this.length++; + if (reverse) + this.nodes.unshift(node); + else + this.nodes.push(node); +}; + + +NodeSet.prototype.unshift = function(node) { + if (!this.length) { + this.length ++; + this.only = node; + return + } + if (this.only){ + var only = this.only; + delete this.only; + this.unshift(only); + this.length --; + } +/*@cc_on + node = this.createWrapper(node); +@*/ + return this._add(node, true); +}; + + +NodeSet.prototype.push = function(node) { + if (!this.length) { + this.length ++; + this.only = node; + return; + } + if (this.only) { + var only = this.only; + delete this.only; + this.push(only); + this.length --; + } +/*@cc_on + node = this.createWrapper(node); +@*/ + return this._add(node); +}; + +NodeSet.prototype.first = function() { + if (this.only) return this.only; +/*@cc_on + if (this._first) return this._first.node; + if (this.nodes.length > 1) this.sort(); + var node = this.nodes[0]; + return node ? node.node : undefined; +@*/ + if (this.nodes.length > 1) this.sort(); + return this.nodes[0]; +}; + +NodeSet.prototype.list = function() { + if (this.only) return [this.only]; + this.sort(); +/*@cc_on + var i, l, nodes, results; + nodes = this.nodes; + results = []; + for (i = 0, l = nodes.length; i < l; i ++) { + results.push(nodes[i].node); + } + return results; +@*/ + return this.nodes; +}; + +NodeSet.prototype.string = function() { + var node = this.only || this.first(); + return node ? NodeUtil.to('string', node) : ''; +}; + +NodeSet.prototype.bool = function() { + return !! (this.length || this.only); +}; + +NodeSet.prototype.number = function() { + return + this.string(); +}; + +NodeSet.prototype.iterator = function(reverse) { + this.sort(); + var nodeset = this; + + if (!reverse) { + var count = 0; + return function() { + if (nodeset.only && count++ == 0) return nodeset.only; +/*@cc_on @if(@_jscript) + var wrapper = nodeset.nodes[count++]; + if (wrapper) return wrapper.node; + return undefined; +@else @*/ + return nodeset.nodes[count++]; +/*@end @*/ + }; + } + else { + var count = 0; + return function() { + var index = nodeset.length - (count++) - 1; + if (nodeset.only && index == 0) return nodeset.only; +/*@cc_on @if(@_jscript) + var wrapper = nodeset.nodes[index]; + if (wrapper) return wrapper.node; + return undefined; +@else @*/ + return nodeset.nodes[index]; +/*@end @*/ + }; + } +}; + + +var install = function(win) { + + win = win || this; + + win.XPathExpression = function(expr) { + if (!expr.length) { + throw Error('no expression'); + } + var lexer = this.lexer = Lexer(expr); + if (lexer.empty()) { + throw Error('no expression'); + } + this.expr = BinaryExpr.parse(lexer); + if (!lexer.empty()) { + throw Error('bad token: ' + lexer.next()); + } + }; + + win.XPathExpression.prototype.evaluate = function(node, type) { + return new XPathResult(this.expr.evaluate(new Ctx(node)), type); + }; + + win.XPathResult = function (value, type) { + if (type == 0) { + switch (typeof value) { + case 'object': type ++; // 4 + case 'boolean': type ++; // 3 + case 'string': type ++; // 2 + case 'number': type ++; // 1 + } + } + + this.resultType = type; + + switch (type) { + case 1: + this.numberValue = value.isNodeSet ? value.number() : +value; + return; + case 2: + this.stringValue = value.isNodeSet ? value.string() : '' + value; + return; + case 3: + this.booleanValue = value.isNodeSet ? value.bool() : !! value; + return; + case 4: case 5: case 6: case 7: + this.nodes = value.list(); + this.snapshotLength = value.length; + this.index = 0; + this.invalidIteratorState = false; + break; + case 8: case 9: + this.singleNodeValue = value.first(); + return; + } + }; + + win.XPathResult.prototype.iterateNext = function() { return this.nodes[this.index++] }; + win.XPathResult.prototype.snapshotItem = function(i) { return this.nodes[i] }; + + win.XPathResult.ANY_TYPE = 0; + win.XPathResult.NUMBER_TYPE = 1; + win.XPathResult.STRING_TYPE = 2; + win.XPathResult.BOOLEAN_TYPE = 3; + win.XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4; + win.XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5; + win.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE = 6; + win.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7; + win.XPathResult.ANY_UNORDERED_NODE_TYPE = 8; + win.XPathResult.FIRST_ORDERED_NODE_TYPE = 9; + + + win.document.createExpression = function(expr) { + return new XPathExpression(expr, null); + }; + + win.document.evaluate = function(expr, context, _, type) { + return document.createExpression(expr, null).evaluate(context, type); + }; +}; + +var win; + +if (config.targetFrame) { + var frame = document.getElementById(config.targetFrame); + if (frame) win = frame.contentWindow; +} + +install(win || window); + +})(); + +// Thanks for reading this source code. We love JavaScript. + diff --git a/lib/Swift/tests/_samples/charsets/iso-8859-1/one.txt b/lib/Swift/tests/_samples/charsets/iso-8859-1/one.txt new file mode 100644 index 0000000..3101178 --- /dev/null +++ b/lib/Swift/tests/_samples/charsets/iso-8859-1/one.txt @@ -0,0 +1,19 @@ +Op mat eraus hinnen beschte, rou zënne schaddreg ké. Ké sin Eisen Kaffi prächteg, den haut esou Fielse wa, Well zielen d'Welt am dir. Aus grousse rëschten d'Stroos do, as dat Kléder gewëss d'Kàchen. Schied gehéiert d'Vioule net hu, rou ke zënter Säiten d'Hierz. Ze eise Fletschen mat, gei as gréng d'Lëtzebuerger. Wäit räich no mat. + +Säiten d'Liewen aus en. Un gëtt bléit lossen wee, da wéi alle weisen Kolrettchen. Et deser d'Pan d'Kirmes vun, en wuel Benn rëschten méi. En get drem ménger beschte, da wär Stad welle. Nun Dach d'Pied do, mä gét ruffen gehéiert. Ze onser ugedon fir, d'Liewen Plett'len ech no, si Räis wielen bereet wat. Iwer spilt fir jo. + +An hin däischter Margréitchen, eng ke Frot brommt, vu den Räis néierens. Da hir Hunn Frot nozegon, rout Fläiß Himmel zum si, net gutt Kaffi Gesträich fu. Vill lait Gaart sou wa, Land Mamm Schuebersonndeg rei do. Gei geet Minutt en, gei d'Leit beschte Kolrettchen et, Mamm fergiess un hun. + +Et gutt Heck kommen oft, Lann rëscht rei um, Hunn rëscht schéinste ke der. En lait zielen schnéiwäiss hir, fu rou botze éiweg Minutt, rem fest gudden schaddreg en. Noper bereet Margréitchen mat op, dem denkt d'Leit d'Vioule no, oft ké Himmel Hämmel. En denkt blénken Fréijor net, Gart Schiet d'Natur no wou. No hin Ierd Frot d'Kirmes. Hire aremt un rou, ké den éiweg wielen Milliounen. + +Mir si Hunn Blénkeg. Ké get ston derfir d'Kàchen. Haut d'Pan fu ons, dé frou löschteg d'Meereische rei. Sou op wuel Léift. Stret schlon grousse gin hu. Mä denkt d'Leit hinnen net, ké gét haut fort rëscht. + +Koum d'Pan hannendrun ass ké, ké den brét Kaffi geplot. Schéi Hären d'Pied fu gét, do d'Mier néierens bei. Rëm päift Hämmel am, wee Engel beschéngt mä. Brommt klinzecht der ke, wa rout jeitzt dén. Get Zalot d'Vioule däischter da, jo fir Bänk päift duerch, bei d'Beem schéinen Plett'len jo. Den haut Faarwen ze, eng en Biereg Kirmesdag, um sin alles Faarwen d'Vioule. + +Eng Hunn Schied et, wat wa Frot fest gebotzt. Bei jo bleiwe ruffen Klarinett. Un Feld klinzecht gét, rifft Margréitchen rem ke. Mir dé Noper duurch gewëss, ston sech kille sin en. Gei Stret d'Wise um, Haus Gart wee as. Monn ménger an blo, wat da Gart gefällt Hämmelsbrot. + +Brommt geplot och ze, dat wa Räis Well Kaffi. Do get spilt prächteg, as wär kille bleiwe gewalteg. Onser frësch Margréitchen rem ke, blo en huet ugedon. Onser Hemecht wär de, hu eraus d'Sonn dat, eise deser hannendrun da och. + +As durch Himmel hun, no fest iw'rem schéinste mir, Hunn séngt Hierz ke zum. Séngt iw'rem d'Natur zum an. Ke wär gutt Grénge. Kënnt gudden prächteg mä rei. Dé dir Blénkeg Klarinett Kolrettchen, da fort muerges d'Kanner wou, main Feld ruffen vu wéi. Da gin esou Zalot gewalteg, gét vill Hemecht blénken dé. + +Haut gréng nun et, nei vu Bass gréng d'Gaassen. Fest d'Beem uechter si gin. Oft vu sinn wellen kréien. Et ass lait Zalot schéinen. \ No newline at end of file diff --git a/lib/Swift/tests/_samples/charsets/utf-8/one.txt b/lib/Swift/tests/_samples/charsets/utf-8/one.txt new file mode 100644 index 0000000..26c94d5 --- /dev/null +++ b/lib/Swift/tests/_samples/charsets/utf-8/one.txt @@ -0,0 +1,22 @@ +Код одно гринÑпана руководишь на. Его вы Ð·Ð½Ð°Ð½Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ðµ. Ты две начать +одиночку, Ñказать оÑнователь удовольÑтвием но миф. Бы какие ÑиÑтема тем. +ПолноÑтью иÑпользует три мы, человек клоунов те наÑ, бы давать творчеÑкую +ÑзотеричеÑÐºÐ°Ñ ÑˆÐµÑ„. + +Мог не помнить никакого ÑÑкономленного, две либо какие пишите бы. Должен +компанию кто те, Ñтот заключалаÑÑŒ проектировщик не Ñ‚Ñ‹. Глупые периоды Ñ‚Ñ‹ +длÑ. Вам который хороший он. Те любых ÐºÑ€ÐµÐ¼Ð½Ð¸Ñ ÐºÐ¾Ð½Ñ†ÐµÐ½Ñ‚Ñ€Ð¸Ñ€ÑƒÑŽÑ‚ÑÑ Ð¼Ð¾Ð³, +Ñобирать принадлежите без вы. + +ДжоÑла меньше хорошего вы миф, за тем году разработки. Даже управлÑющим +руководители был не. Три коде выпуÑкать заботитьÑÑ Ð½Ñƒ. То его ÑиÑтема +удовольÑтвием безоÑтановочно, или Ñ‚Ñ‹ главной процеÑÑорах. Мы без джоÑл +Ð·Ð½Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ‚, Ñтатьи оÑтальные мы ещё. + +Ðих руÑÑком каÑаетÑÑ Ð¿Ð¾Ñкольку по, образование должником +ÑиÑтематизированный ну мои. Прийти кандидата универÑитет но наÑ, Ð´Ð»Ñ Ð±Ñ‹ +должны никакого, биг многие причин Ð¸Ð½Ñ‚ÐµÑ€Ð²ÑŒÑŽÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð°. + +Тем до плиту почему. Вот учёт такие одного бы, об биг разным внешних +промежуток. Ð’Ð°Ñ Ð´Ð¾ какому возможноÑтей безответÑтвенный, были погодите бы +его, по них глупые долгий количеÑтва. diff --git a/lib/Swift/tests/_samples/charsets/utf-8/three.txt b/lib/Swift/tests/_samples/charsets/utf-8/three.txt new file mode 100644 index 0000000..c81ccd5 --- /dev/null +++ b/lib/Swift/tests/_samples/charsets/utf-8/three.txt @@ -0,0 +1,45 @@ +Αν ήδη διάβασε γλιτώσει μεταγλωτίσει, αυτήν θυμάμαι μου μα. Την κατάσταση χÏησιμοποίησέ να! Τα διαφοÏά φαινόμενο διολισθήσεις πες, υψηλότεÏη Ï€Ïοκαλείς πεÏισσότεÏες όχι κι. Με ελέγχου γίνεται σας, μικÏής δημιουÏγοÏν τη του. Τις τα γÏάψει εικόνες απαÏάδεκτη? + +Îα ότι Ï€Ïώτοι απαÏαίτητο. Άμεση πετάνε κακόκεφος τον ÏŽÏ‚, να χώÏου πιθανότητες του. Το μέχÏι οÏίστε λιγότεÏους σας. Πω ναί φυσικά εικόνες. + +Μου οι κώδικα αποκλειστικοÏÏ‚, λες το μάλλον συνεχώς. Îέου σημεία απίστευτα σας μα. ΧÏόνου μεταγλωτιστής σε νέα, τη τις πιάνει μποÏοÏσες Ï€ÏογÏαμματιστές. Των κάνε βγαίνει εντυπωσιακό τα? ΚÏατάει τεσσαÏών δυστυχώς της κι, ήδη υψηλότεÏη εξακολουθεί τα? + +ÎÏα πετάνε μποÏοÏσε λιγότεÏους αν, τα απαÏάδεκτη συγχωνευτεί Ïοή. Τη έγÏαψες συνηθίζουν σαν. Όλα με υλικό στήλες χειÏότεÏα. Ανώδυνη δουλέψει επί ως, αν διαδίκτυο εσωτεÏικών παÏάγοντες από. ΚεντÏικό επιτυχία πες το. + +Πω ναι λέει τελειώσει, έξι ως έÏγων τελειώσει. Με αÏχεία βουτήξουν ανταγωνιστής ÏŽÏα, Ï€Î¿Î»Ï Î³Ïαφικά σελίδων τα στη. ÎŒÏο οέλεγχος δημιουÏγοÏν δε, ας θέλεις ελέγχου συντακτικό ÏŒÏο! Της θυμάμαι επιδιόÏθωση τα. Για μποÏοÏσε πεÏισσότεÏο αν, μέγιστη σημαίνει αποφάσισε τα του, άτομο αποτελέσει τι στα. + +Τι στην αφήσεις διοίκηση στη. Τα εσφαλμένη δημιουÏγια επιχείÏιση έξι! Βήμα μαγικά εκτελέσει ανά τη. Όλη αφήσεις συνεχώς εμποÏικά αν, το λες κόλπα επιτυχία. Ότι οι ζώνη κειμένων. ÎŒÏο κι Ïωτάει γÏαμμής πελάτες, τελειώσει διολισθήσεις καθυστεÏοÏσε αν εγώ? Τι πετοÏν διοίκηση Ï€Ïοβλήματα ήδη. + +Τη γλιτώσει Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Î¼Î¹Î±. Πω έξι δημιουÏγια πιθανότητες, ως πέντε ελέγχους εκτελείται λες. Πως εÏωτήσεις διοικητικό συγκεντÏωμένοι οι, ας συνεχώς διοικητικό αποστηθίσει σαν. Δε Ï€Ïώτες συνεχώς διολισθήσεις έχω, από τι κανένας βουτήξουν, γειτονιάς Ï€Ïοσεκτικά ανταγωνιστής κι σαν. + +ΔημιουÏγια συνηθίζουν κλπ τι? Όχι ποσοστό διακοπής κι. Κλπ φακέλους δεδομένη εξοÏγιστικά θα? Υποψήφιο καθοÏίζουν με όλη, στα πήÏε Ï€Ïοσοχή εταιÏείες πω, ÏŽÏ‚ τον συνάδελφος διοικητικό δημιουÏγήσεις! ΔοÏλευε επιτίθενται σας θα, με ένας παÏαγωγικής ένα, να ναι σημεία μέγιστη απαÏάδεκτη? + +Σας τεσσαÏών συνεντεÏξης τη, αÏπάζεις σίγουÏος μη για', επί τοπικές εντολές ακοÏσει θα? Ως δυστυχής μεταγλωτιστής όλη, να την είχαν σφάλμα απαÏαίτητο! Μην ÏŽÏ‚ άτομο διοÏθώσει χÏησιμοποιοÏνταν. Δεν τα κόλπα πετάξαμε, μη που άγχος Ï…ÏŒÏκη άμεση, Î±Ï†Î¿Ï Î´Ï…ÏƒÏ„Ï…Ï‡ÏŽÏ‚ διακόψουμε ÏŒÏο αν! Όλη μαγικά πετάνε επιδιοÏθώσεις δε, Ïοή φυσικά αποτελέσει πω. + +ΆπειÏα παÏαπάνω φαινόμενο πω ÏŽÏα, σαν πόÏτες κÏατήσουν συνηθίζουν ως. Κι ÏŽÏα Ï„Ïέξει είχαμε εφαÏμογή. Απλό σχεδιαστής μεταγλωτιστής ας επί, τις τα όταν έγÏαψες γÏαμμής? Όλα κάνεις συνάδελφος εÏγαζόμενοι θα, χαÏÏ„Î¹Î¿Ï Ï‡Î±Î¼Î·Î»ÏŒÏ‚ τα Ïοή. Ως ναι ÏŒÏοφο έÏθει, μην πελάτες αποφάσισε μεταφÏαστής με, να βιαστικά εκδόσεις αναζήτησης λες. Των φταίει εκθέσεις Ï€Ïοσπαθήσεις οι, σπίτι αποστηθίσει ας λες? + +ÎÏ‚ που υπηÏεσία απαÏαίτητο δημιουÏγείς. Μη άÏα χαÏά καθώς νÏχτας, πω ματ μπουν είχαν. Άμεση δημιουÏγείς ÏŽÏ‚ Ïοή, γÏάψει γÏαμμής σίγουÏος στα τι! Αν Î±Ï†Î¿Ï Ï€Ïώτοι εÏγαζόμενων ναί. + +Άμεση διοÏθώσεις με δÏο? Έχουν παÏάδειγμα των θα, μου έÏθει θυμάμαι πεÏισσότεÏο το. Ότι θα Î±Ï†Î¿Ï Ï‡Ïειάζονται πεÏισσότεÏες. Σαν συνεχώς πεÏίπου οι. + +ÎÏ‚ Ï€Ïώτης πετάξαμε λες, ÏŒÏο κι Ï€Ïώτες ζητήσεις δυστυχής. Ανά χÏόνου διακοπή επιχειÏηματίες ας, ÏŽÏ‚ μόλις άτομο χειÏότεÏα ÏŒÏο, κÏατάει σχεδιαστής Ï€Ïοσπαθήσεις νέο το. Πουλάς Ï€Ïοσθέσει όλη πω, Ï„Ïπου χαÏακτηÏιστικό εγώ σε, πω πιο δοÏλευε αναζήτησης? ΑναφοÏά δίνοντας σαν μη, μάθε δεδομένη εσωτεÏικών με ναι, αναφέÏονται πεÏιβάλλοντος ÏŽÏα αν. Και λέει απόλαυσε τα, που το ÏŒÏοφο Ï€ÏοσπαθοÏν? + +Πάντα χÏόνου χÏήματα ναι το, σαν σωστά θυμάμαι σκεφτείς τα. Μα αποτελέσει ανεπιθÏμητη την, πιο το τέτοιο ατόμου, τη των Ï„Ïόπο εÏγαλείων επιδιόÏθωσης. ΠεÏιβάλλον παÏαγωγικής σου κι, κλπ οι Ï„Ïπου κακόκεφους αποστηθίσει, δε των πλέον Ï„Ïόποι. Πιθανότητες χαÏακτηÏιστικών σας κι, γÏαφικά δημιουÏγήσεις μια οι, πω πολλοί εξαÏτάται Ï€Ïοσεκτικά εδώ. Σταματάς παÏάγοντες για' ÏŽÏ‚, στις Ïωτάει το ναι! ΚαÏέκλα ζητήσεις συνδυασμοÏÏ‚ τη ήδη! + +Για μαγικά συνεχώς ακοÏσει το. Σταματάς Ï€Ïοϊόντα βουτήξουν ÏŽÏ‚ Ïοή. Είχαν Ï€Ïώτες οι ναι, μα λες αποστηθίσει ανακαλÏπτεις. ÎŒÏοφο άλγεβÏα παÏαπάνω εδώ τη, Ï€Ïόσληψη λαμβάνουν καταλάθος ήδη ας? Ως και εισαγωγή κÏατήσουν, ένας κακόκεφους κι μας, όχι κώδικάς παίξουν πω. Πω νέα κÏατάει εκφÏάσουν, τότε τελικών τη όχι, ας της Ï„Ïέξει αλλάζοντας αποκλειστικοÏÏ‚. + +Ένας βιβλίο σε άÏα, ναι ως γÏάψει ταξινομεί διοÏθώσεις! Εδώ να γεγονός συγγÏαφείς, ÏŽÏ‚ ήδη διακόψουμε επιχειÏηματίες? Ότι πακέτων εσφαλμένη κι, θα ÏŒÏο κόλπα παÏαγωγικής? Αν έχω κεντÏικό υψηλότεÏη, κι δεν ίδιο πετάνε παÏατηÏοÏμενη! Που λοιπόν σημαντικό μα, Ï€Ïοκαλείς χειÏοκÏοτήματα ως όλα, μα επί κόλπα άγχος γÏαμμές! Δε σου κάνεις βουτήξουν, μη έÏγων επενδυτής χÏησιμοποίησέ στα, ως του Ï€Ïώτες διάσημα σημαντικό. + +Βιβλίο τεÏάστιο Ï€ÏοκÏπτουν σαν το, σαν Ï„Ïόπο επιδιόÏθωση ας. Είχαν Ï€Ïοσοχή Ï€Ïοσπάθεια κι ματ, εδώ ως έτσι σελίδων συζήτηση. Και στην βγαίνει εσφαλμένη με, δυστυχής παÏάδειγμα δε μας, από σε Ï…ÏŒÏκη επιδιόÏθωσης. Îέα πω νέου πιθανό, στήλες συγγÏαφείς μπαίνοντας μα για', το Ïωτήσει κακόκεφους της? Μου σε αÏέσει συγγÏαφής συγχωνευτεί, μη μου Ï…ÏŒÏκη ξέχασε διακοπής! ÎÏ‚ επί αποφάσισε αποκλειστικοÏÏ‚ χÏησιμοποιώντας, χÏήματα σελίδων ταξινομεί ναι με. + +Μη ανά γÏαμμή απόλαυσε, πω ναι μάτσο διασφαλίζεται. Τη έξι μόλις εÏγάστηκε δημιουÏγοÏν, έκδοση αναφοÏά δυσκολότεÏο οι νέο. Σας ως μποÏοÏσε παÏάδειγμα, αν ότι δοÏλευε μποÏοÏσε αποκλειστικοÏÏ‚, πιο λέει βουτήξουν διοÏθώσει ως. Έχω τελευταία κακόκεφους ας, όσο εÏγαζόμενων δημιουÏγήσεις τα. + +Του αν δουλέψει μποÏοÏσε, πετοÏν χαμηλός εδώ ας? ΚÏκλο Ï„Ïπους με που, δεν σε έχουν συνεχώς χειÏότεÏα, τις τι απαÏάδεκτη συνηθίζουν? Θα μην τους αυτήν, τη ένα πήÏε πακέτων, κι Ï€ÏοκÏπτουν πεÏιβάλλον πως. Μα για δουλέψει απόλαυσε εφαμοÏγής, ÏŽÏ‚ εδώ σημαίνει μποÏοÏσες, άμεση ακοÏσει Ï€Ïοσοχή τη εδώ? + +Στα δώσε αθόÏυβες λιγότεÏους οι, δε αναγκάζονται αποκλειστικοÏÏ‚ όλα! Ας μπουν διοικητικό μια, πάντα ελέγχου διοÏθώσεις ÏŽÏ‚ τον. Ότι πήÏε κανόνα μα. Που άτομα κάνεις δημιουÏγίες τα, οι μας Î±Ï†Î¿Ï ÎºÏŒÎ»Ï€Î± Ï€ÏογÏαμματιστής, Î±Ï†Î¿Ï Ï‰Ïαίο Ï€ÏοκÏπτουν στα ως. Θέμα χÏησιμοποιήσει αν όλα, του τα άλγεβÏα σελίδων. Τα ότι ανώδυνη δυστυχώς συνδυασμοÏÏ‚, μας οι πάντα γνωÏίζουμε ανταγωνιστής, όχι τα δοκιμάσεις σχεδιαστής! Στην συνεντεÏξης επιδιόÏθωση πιο τα, μα από πουλάς πεÏιβάλλον παÏαγωγικής. + +Έχουν μεταγλωτίσει σε σας, σε πάντα Ï€Ïώτης μειώσει των, γÏάψει Ïουτίνα δυσκολότεÏο ήδη μα? Ταξινομεί διοÏθώσεις να μας. Θα της Ï€ÏοσπαθοÏν πεÏιεχόμενα, δε έχω τοπικές στέλνοντάς. Ανά δε αλφα άμεση, κάποιο Ïωτάει γνωÏίζουμε πω στη, φÏάση μαγικά συνέχεια δε δÏο! Αν είχαμε μειώσει Ïοή, μας μετÏάει καθυστεÏοÏσε επιδιοÏθώσεις μη. Χάος Ï…ÏŒÏκη κεντÏικό έχω σε, ανά πεÏίπου αναγκάζονται πω. + +Όσο επιστÏέφουν χÏονοδιαγÏάμματα μη. Πως ωÏαίο κακόκεφος διαχειÏιστής ως, τις να διακοπής αναζήτησης. Κάποιο ποσοστό ταξινομεί επί τη? Μάθε άμεση αλλάζοντας δÏο με, μου νέου πάντα να. + +Πω του δυστυχώς πιθανότητες. Κι Ïωτάει υψηλότεÏη δημιουÏγια ότι, πω εισαγωγή τελευταία απομόνωση ναι. Των ζητήσεις γνωÏίζουμε ÏŽÏ‚? Για' μη παÏαδοτέου αναφέÏονται! Ύψος παÏαγωγικά Ïοή ως, φυσικά διάβασε εικόνες όσο σε? Δεν Ï…ÏŒÏκη διοÏθώσεις επεξεÏγασία θα, ως μέση σÏστημα χÏησιμοποιήσει τις. \ No newline at end of file diff --git a/lib/Swift/tests/_samples/charsets/utf-8/two.txt b/lib/Swift/tests/_samples/charsets/utf-8/two.txt new file mode 100644 index 0000000..2443fc4 --- /dev/null +++ b/lib/Swift/tests/_samples/charsets/utf-8/two.txt @@ -0,0 +1,3 @@ +रखति आवशà¥à¤¯à¤•à¤¤ पà¥à¤°à¥‡à¤°à¤¨à¤¾ मà¥à¤–à¥à¤¯à¤¤à¤¹ हिंदी किà¤à¤²à¥‹à¤— असकà¥à¤·à¤® कारà¥à¤¯à¤²à¤¯ करते विवरण किके मानसिक दिनांक पà¥à¤°à¥à¤µ संसाध à¤à¤µà¤®à¥ कà¥à¤¶à¤²à¤¤à¤¾ अमितकà¥à¤®à¤¾à¤° पà¥à¤°à¥‹à¤¤à¥à¤¸à¤¾à¤¹à¤¿à¤¤ जनित देखने उदेशीत विकसित बलवान बà¥à¤°à¥Œà¤¶à¤° किà¤à¤²à¥‹à¤— विशà¥à¤²à¥‡à¤·à¤£ लोगो कैसे जागरà¥à¤• पà¥à¤°à¤µà¥à¤°à¥à¤¤à¤¿ पà¥à¤°à¥‹à¤¤à¥à¤¸à¤¾à¤¹à¤¿à¤¤ सदसà¥à¤¯ आवशà¥à¤¯à¤•à¤¤ पà¥à¤°à¤¸à¤¾à¤°à¤¨ उपलबà¥à¤§à¤¤à¤¾ अथवा हिंदी जनित दरà¥à¤¶à¤¾à¤¤à¤¾ यनà¥à¤¤à¥à¤°à¤¾à¤²à¤¯ बलवान अतित सहयोग शà¥à¤°à¥à¤†à¤¤ सभीकà¥à¤› माहितीवानीजà¥à¤¯ लिये खरिदे है।अभी à¤à¤•à¤¤à¥à¤°à¤¿à¤¤ समà¥à¤ªà¤°à¥à¤• रिती मà¥à¤¶à¥à¤•à¤¿à¤² पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤• भेदनकà¥à¤·à¤®à¤¤à¤¾ विशà¥à¤µ उनà¥à¤¹à¥‡ गटको दà¥à¤µà¤¾à¤°à¤¾ तकरीबन + +विशà¥à¤µ दà¥à¤µà¤¾à¤°à¤¾ वà¥à¤¯à¤¾à¤–à¥à¤¯à¤¾ सके। आजपर वातावरण वà¥à¤¯à¤¾à¤–à¥à¤¯à¤¾à¤¨ पहोच। हमारी कीसे पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤• विचारशिलता पà¥à¤°à¥à¤µ करती कमà¥à¤ªà¥à¤¯à¥à¤Ÿà¤° भेदनकà¥à¤·à¤®à¤¤à¤¾ लिये बलवान औरà¥à¥ªà¥«à¥¦ यायेका वारà¥à¤¤à¤¾à¤²à¤¾à¤ª सà¥à¤šà¤¨à¤¾ भारत शà¥à¤°à¥à¤†à¤¤ लाभानà¥à¤µà¤¿à¤¤ पढाठसंसà¥à¤¥à¤¾ वरà¥à¤£à¤¿à¤¤ मारà¥à¤—दरà¥à¤¶à¤¨ चà¥à¤¨à¤¨à¥‡ \ No newline at end of file diff --git a/lib/Swift/tests/_samples/files/data.txt b/lib/Swift/tests/_samples/files/data.txt new file mode 100644 index 0000000..3f35021 --- /dev/null +++ b/lib/Swift/tests/_samples/files/data.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/Swift/tests/_samples/files/textfile.zip b/lib/Swift/tests/_samples/files/textfile.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a580ecbf81ff98445bc5c6d0fce7a9a301f0e39 GIT binary patch literal 202 zcmWIWW@h1HU}9ikII%0tqR&rhgC39#!aNM33?-=*C25&Csd^<9C7~gl49w;G_PK*_ zXax(ySH`c5AsLy)3P4nlSX82rpQezg5L}*_R-)jW2-H=iP-db~oSUDWs!)skipUnless( + SWIFT_TMP_DIR, 'Cannot run test without a writable directory to use (' . + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + public function setUp() + { + $this->_tmpDir = SWIFT_TMP_DIR; + $this->_testFile = $this->_tmpDir . '/swift-test-file' . __CLASS__; + file_put_contents($this->_testFile, 'abcdefghijklm'); + } + + public function tearDown() + { + unlink($this->_testFile); + } + + public function testFileDataCanBeRead() + { + $file = $this->_createFileStream($this->_testFile); + $str = ''; + while (false !== $bytes = $file->read(8192)) + { + $str .= $bytes; + } + $this->assertEqual('abcdefghijklm', $str); + } + + public function testFileDataCanBeReadSequentially() + { + $file = $this->_createFileStream($this->_testFile); + $this->assertEqual('abcde', $file->read(5)); + $this->assertEqual('fghijklm', $file->read(8)); + $this->assertFalse($file->read(1)); + } + + public function testFilenameIsReturned() + { + $file = $this->_createFileStream($this->_testFile); + $this->assertEqual($this->_testFile, $file->getPath()); + } + + public function testFileCanBeWrittenTo() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->write('foobar'); + $this->assertEqual('foobar', $file->read(8192)); + } + + public function testReadingFromThenWritingToFile() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->write('foobar'); + $this->assertEqual('foobar', $file->read(8192)); + $file->write('zipbutton'); + $this->assertEqual('zipbutton', $file->read(8192)); + } + + public function testWritingToFileWithCanonicalization() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->addFilter($this->_createFilter(array("\r\n", "\r"), "\n"), 'allToLF'); + $file->write("foo\r\nbar\r"); + $file->write("\nzip\r\ntest\r"); + $file->flushBuffers(); + $this->assertEqual("foo\nbar\nzip\ntest\n", file_get_contents($this->_testFile)); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + -> one($is2)->write('y') + ); + + $file->bind($is1); + $file->bind($is2); + + $file->write('x'); + $file->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->flushBuffers() + -> one($is2)->flushBuffers() + ); + + $file->bind($is1); + $file->bind($is2); + + $file->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + ); + + $file->bind($is1); + $file->bind($is2); + + $file->write('x'); + + $file->unbind($is2); + + $file->write('y'); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + private function _createMockInputStream() + { + return $this->_mock('Swift_InputByteStream'); + } + + private function _createFileStream($file, $writable = false) + { + return new Swift_ByteStream_FileByteStream($file, $writable); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php new file mode 100644 index 0000000..7b71b53 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php @@ -0,0 +1,204 @@ +_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testCreatingUtf8Reader() + { + foreach (array('utf8', 'utf-8', 'UTF-8', 'UTF8') as $utf8) + { + $reader = $this->_factory->getReaderFor($utf8); + $this->assertIsA($reader, $this->_prefix . 'Utf8Reader'); + } + } + + public function testCreatingIso8859XReaders() + { + $charsets = array(); + foreach (range(1, 16) as $number) + { + foreach (array('iso', 'iec') as $body) + { + $charsets[] = $body . '-8859-' . $number; + $charsets[] = $body . '8859-' . $number; + $charsets[] = strtoupper($body) . '-8859-' . $number; + $charsets[] = strtoupper($body) . '8859-' . $number; + } + } + + foreach ($charsets as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingWindows125XReaders() + { + $charsets = array(); + foreach (range(0, 8) as $number) + { + $charsets[] = 'windows-125' . $number; + $charsets[] = 'windows125' . $number; + $charsets[] = 'WINDOWS-125' . $number; + $charsets[] = 'WINDOWS125' . $number; + } + + foreach ($charsets as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingCodePageReaders() + { + $charsets = array(); + foreach (range(0, 8) as $number) + { + $charsets[] = 'cp-125' . $number; + $charsets[] = 'cp125' . $number; + $charsets[] = 'CP-125' . $number; + $charsets[] = 'CP125' . $number; + } + + foreach (array(437, 737, 850, 855, 857, 858, 860, + 861, 863, 865, 866, 869) as $number) + { + $charsets[] = 'cp-' . $number; + $charsets[] = 'cp' . $number; + $charsets[] = 'CP-' . $number; + $charsets[] = 'CP' . $number; + } + + foreach ($charsets as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingAnsiReader() + { + foreach (array('ansi', 'ANSI') as $ansi) + { + $reader = $this->_factory->getReaderFor($ansi); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingMacintoshReader() + { + foreach (array('macintosh', 'MACINTOSH') as $mac) + { + $reader = $this->_factory->getReaderFor($mac); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingKOIReaders() + { + $charsets = array(); + foreach (array('7', '8-r', '8-u', '8u', '8r') as $end) + { + $charsets[] = 'koi-' . $end; + $charsets[] = 'koi' . $end; + $charsets[] = 'KOI-' . $end; + $charsets[] = 'KOI' . $end; + } + + foreach ($charsets as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingIsciiReaders() + { + foreach (array('iscii', 'ISCII', 'viscii', 'VISCII') as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingMIKReader() + { + foreach (array('mik', 'MIK') as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingCorkReader() + { + foreach (array('cork', 'CORK', 't1', 'T1') as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingUcs2Reader() + { + foreach (array('ucs-2', 'UCS-2', 'ucs2', 'UCS2') as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(2, $reader->getInitialByteSize()); + } + } + + public function testCreatingUtf16Reader() + { + foreach (array('utf-16', 'UTF-16', 'utf16', 'UTF16') as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(2, $reader->getInitialByteSize()); + } + } + + public function testCreatingUcs4Reader() + { + foreach (array('ucs-4', 'UCS-4', 'ucs4', 'UCS4') as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(4, $reader->getInitialByteSize()); + } + } + + public function testCreatingUtf32Reader() + { + foreach (array('utf-32', 'UTF-32', 'utf32', 'UTF32') as $charset) + { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(4, $reader->getInitialByteSize()); + } + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php new file mode 100644 index 0000000..2b7272d --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php @@ -0,0 +1,28 @@ +listItems() as $itemName) + { + try + { + $di->lookup($itemName); + } + catch (Swift_DependencyException $e) + { + $this->fail($e->getMessage()); + } + } + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php new file mode 100644 index 0000000..ff47f6c --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php @@ -0,0 +1,15 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../_samples/charsets'); + $this->_encoder = new Swift_Encoder_Base64Encoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $this->_encoder->encodeString($text); + + $this->assertEqual( + base64_decode($encodedText), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php new file mode 100644 index 0000000..114c314 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php @@ -0,0 +1,65 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_ArrayCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $encoder->encodeString($text); + + $this->assertEqual( + quoted_printable_decode($encodedText), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php new file mode 100644 index 0000000..102b021 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php @@ -0,0 +1,63 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_ArrayCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $encoder->encodeString($text); + + $this->assertEqual( + urldecode(implode('', explode("\r\n", $encodedText))), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/EncodingAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/EncodingAcceptanceTest.php new file mode 100644 index 0000000..28468d0 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/EncodingAcceptanceTest.php @@ -0,0 +1,33 @@ +assertEqual('7bit', $encoder->getName()); + } + + public function testGet8BitEncodingReturns8BitEncoder() + { + $encoder = Swift_Encoding::get8BitEncoding(); + $this->assertEqual('8bit', $encoder->getName()); + } + + public function testGetQpEncodingReturnsQpEncoder() + { + $encoder = Swift_Encoding::getQpEncoding(); + $this->assertEqual('quoted-printable', $encoder->getName()); + } + + public function testGetBase64EncodingReturnsBase64Encoder() + { + $encoder = Swift_Encoding::getBase64Encoding(); + $this->assertEqual('base64', $encoder->getName()); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php new file mode 100644 index 0000000..6f7e16d --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php @@ -0,0 +1,182 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + } + + public function testStringDataCanBeSetAndFetched() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('whatever', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testing', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('abcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = new Swift_ByteStream_ArrayByteStream(); + $os1->write('abcdef'); + + $os2 = new Swift_ByteStream_ArrayByteStream(); + $os2->write('xyzuvw'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testabcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_cache->exportToByteStream($this->_key1, 'foo', $is); + + $string = ''; + while (false !== $bytes = $is->read(8192)) + { + $string .= $bytes; + } + + $this->assertEqual('test', $string); + } + + public function testKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar')); + $this->_cache->clearAll($this->_key1); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar')); + } + + public function testKeyCacheInputStream() + { + $is = $this->_cache->getInputByteStream($this->_key1, 'foo'); + $is->write('abc'); + $is->write('xyz'); + $this->assertEqual('abcxyz', $this->_cache->getString($this->_key1, 'foo')); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php new file mode 100644 index 0000000..e849ac0 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php @@ -0,0 +1,192 @@ +skipUnless( + SWIFT_TMP_DIR, 'Cannot run test without a writable directory to use (' . + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + public function setUp() + { + $this->_key1 = uniqid(microtime(true), true); + $this->_key2 = uniqid(microtime(true), true); + $this->_cache = new Swift_KeyCache_DiskKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream(), + SWIFT_TMP_DIR + ); + } + + public function testStringDataCanBeSetAndFetched() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('whatever', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testing', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('abcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = new Swift_ByteStream_ArrayByteStream(); + $os1->write('abcdef'); + + $os2 = new Swift_ByteStream_ArrayByteStream(); + $os2->write('xyzuvw'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testabcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_cache->exportToByteStream($this->_key1, 'foo', $is); + + $string = ''; + while (false !== $bytes = $is->read(8192)) + { + $string .= $bytes; + } + + $this->assertEqual('test', $string); + } + + public function testKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar')); + $this->_cache->clearAll($this->_key1); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar')); + } + + public function testKeyCacheInputStream() + { + $is = $this->_cache->getInputByteStream($this->_key1, 'foo'); + $is->write('abc'); + $is->write('xyz'); + $this->assertEqual('abcxyz', $this->_cache->getString($this->_key1, 'foo')); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/MessageAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/MessageAcceptanceTest.php new file mode 100644 index 0000000..0126a57 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/MessageAcceptanceTest.php @@ -0,0 +1,59 @@ +_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $message->addPart('foo', 'text/plain', 'iso-8859-1'); + $message->addPart('test foo', 'text/html', 'iso-8859-1'); + + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'test foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + // -- Private helpers + + protected function _createMessage() + { + Swift_DependencyContainer::getInstance() + ->register('properties.charset')->asValue(null); + return Swift_Message::newInstance(); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php new file mode 100644 index 0000000..745f5f7 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php @@ -0,0 +1,134 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder) + ); + } + + public function testDispositionIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setDisposition('inline'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline' . "\r\n", + $attachment->toString() + ); + } + + public function testDispositionIsAttachmentByDefault() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment' . "\r\n", + $attachment->toString() + ); + } + + public function testFilenameIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n", + $attachment->toString() + ); + } + + public function testSizeIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; size=12340' . "\r\n", + $attachment->toString() + ); + } + + public function testMultipleParametersInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf; size=12340' . "\r\n", + $attachment->toString() + ); + } + + public function testEndToEnd() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setSize(12340); + $attachment->setBody('abcd'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf; size=12340' . "\r\n" . + "\r\n" . + base64_encode('abcd'), + $attachment->toString() + ); + } + + // -- Private helpers + + protected function _createAttachment() + { + $entity = new Swift_Mime_Attachment( + $this->_headers, + $this->_contentEncoder, + $this->_cache + ); + return $entity; + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..1abe849 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php @@ -0,0 +1,70 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) + { + $encoded .= $bytes; + } + + $this->assertEqual( + base64_decode($encoded), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..b3bcdc7 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php @@ -0,0 +1,109 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit'); + } + + public function testEncodingAndDecodingSamplesString() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $this->_encoder->encodeString($text); + + $this->assertEqual( + $encodedText, $text, + '%s: Encoded string should be identical to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + + public function testEncodingAndDecodingSamplesByteStream() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) + { + $encoded .= $bytes; + } + + $this->assertEqual( + $encoded, $text, + '%s: Encoded string should be identical to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..64834c4 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php @@ -0,0 +1,163 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_NgCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) + { + $encoded .= $bytes; + } + + $this->assertEqual( + quoted_printable_decode($encoded), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + + public function testEncodingAndDecodingSamplesFromDiConfiguredInstance() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) + { + if (substr($encodingDir, 0, 1) == '.') + { + continue; + } + + $encoding = $encodingDir; + $encoder = $this->_createEncoderFromContainer(); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) + { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) + { + if (substr($sampleFile, 0, 1) == '.') + { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) + { + $encoded .= $bytes; + } + + $this->assertEqual( + str_replace("\r\n", "\n", quoted_printable_decode($encoded)), str_replace("\r\n", "\n", $text), + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + + public function testEncodingLFTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\nb\r\nc", $encoder->encodeString("a\nb\nc")); + } + + public function testEncodingCRTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\nb\r\nc", $encoder->encodeString("a\rb\rc")); + } + + public function testEncodingLFCRTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\n\r\nb\r\n\r\nc", $encoder->encodeString("a\n\rb\n\rc")); + } + + public function testEncodingCRLFTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\nb\r\nc", $encoder->encodeString("a\r\nb\r\nc")); + } + + // -- Private Methods + + private function _createEncoderFromContainer() + { + return Swift_DependencyContainer::getInstance() + ->lookup('mime.qpcontentencoder') + ; + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php new file mode 100644 index 0000000..fe4c115 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php @@ -0,0 +1,147 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder) + ); + } + + public function testContentIdIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $file->setContentType('application/pdf'); + $file->setId('foo@bar'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline' . "\r\n" . + 'Content-ID: ' . "\r\n", + $file->toString() + ); + } + + public function testDispositionIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setDisposition('attachment'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testFilenameIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=foo.pdf' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testSizeIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; size=12340' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testMultipleParametersInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $file->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=foo.pdf; size=12340' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testEndToEnd() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $file->setSize(12340); + $file->setBody('abcd'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=foo.pdf; size=12340' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n" . + "\r\n" . + base64_encode('abcd'), + $file->toString() + ); + } + + // -- Private helpers + + protected function _createEmbeddedFile() + { + $entity = new Swift_Mime_EmbeddedFile( + $this->_headers, + $this->_contentEncoder, + $this->_cache + ); + return $entity; + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php new file mode 100644 index 0000000..f8e6676 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php @@ -0,0 +1,140 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_QpContentEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'), + new Swift_StreamFilters_ByteArrayReplacementFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ) + ); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder) + ); + } + + public function testCharsetIsSetInHeader() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setBody('foobar'); + $this->assertEqual( + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foobar', + $part->toString() + ); + } + + public function testFormatIsSetInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setFormat('flowed'); + $part->setBody('> foobar'); + $this->assertEqual( + 'Content-Type: text/plain; format=flowed' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + '> foobar', + $part->toString() + ); + } + + public function testDelSpIsSetInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setDelSp(true); + $part->setBody('foobar'); + $this->assertEqual( + 'Content-Type: text/plain; delsp=yes' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foobar', + $part->toString() + ); + } + + public function testAll3ParamsInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setFormat('fixed'); + $part->setDelSp(true); + $part->setBody('foobar'); + $this->assertEqual( + 'Content-Type: text/plain; charset=utf-8; format=fixed; delsp=yes' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foobar', + $part->toString() + ); + } + + public function testBodyIsCanonicalized() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setBody("foobar\r\rtest\ning\r"); + $this->assertEqual( + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + "foobar\r\n" . + "\r\n" . + "test\r\n" . + "ing\r\n", + $part->toString() + ); + } + + // -- Private helpers + + protected function _createMimePart() + { + $entity = new Swift_Mime_MimePart( + $this->_headers, + $this->_contentEncoder, + $this->_cache + ); + return $entity; + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php new file mode 100644 index 0000000..3eab521 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php @@ -0,0 +1,1256 @@ +setCharset(null); //TODO: Test with the charset defined + } + + public function testBasicHeaders() + { + /* -- RFC 2822, 3.6. + */ + + $message = $this->_createMessage(); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString(), + '%s: Only required headers, and non-empty headers should be displayed' + ); + } + + public function testSubjectIsDisplayedIfSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testDateCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $id = $message->getId(); + $message->setDate(1234); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', 1234) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMessageIdCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setId('foo@bar'); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: ' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testContentTypeCanBeChanged() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testCharsetCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $message->setCharset('iso-8859-1'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testFormatCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFormat('flowed'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain; format=flowed' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testEncoderCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $message->setEncoder( + new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit') + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: 7bit' . "\r\n", + $message->toString() + ); + } + + public function testFromAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom('chris.corbyn@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: chris.corbyn@swiftmailer.org' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testFromAddressCanBeSetWithName() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleFromAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn , mark@swiftmailer.org' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testReturnPathAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testEmptyReturnPathHeaderCanBeUsed() + { + $message = $this->_createMessage(); + $message->setReturnPath(''); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: <>' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testSenderCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setSender('chris.corbyn@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Sender: chris.corbyn@swiftmailer.org' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testSenderCanBeSetWithName() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setSender(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Sender: Chris ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testReplyToCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array('chris@w3style.co.uk'=>'Myself')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleReplyAddressCanBeUsed() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testToAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo('mark@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleToAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testCcAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc('john@some-site.com'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: john@some-site.com' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleCcAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: John West , Big Fred ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testBccAddressCanBeSet() + { + //Obviously Transports need to setBcc(array()) and send to each Bcc recipient + // separately in accordance with RFC 2822/2821 + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred' + )); + $message->setBcc('x@alphabet.tld'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: John West , Big Fred ' . "\r\n" . + 'Bcc: x@alphabet.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleBccAddressesCanBeSet() + { + //Obviously Transports need to setBcc(array()) and send to each Bcc recipient + // separately in accordance with RFC 2822/2821 + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred' + )); + $message->setBcc(array('x@alphabet.tld', 'a@alphabet.tld' => 'A')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: John West , Big Fred ' . "\r\n" . + 'Bcc: x@alphabet.tld, A ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testStringBodyIsAppended() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setBody( + 'just a test body' . "\r\n" . + 'with a new line' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'just a test body' . "\r\n" . + 'with a new line', + $message->toString() + ); + } + + public function testStringBodyIsEncoded() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setBody( + 'Just s' . pack('C*', 0xC2, 0x01, 0x01) . 'me multi-' . "\r\n" . + 'line message!' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'Just s=C2=01=01me multi-' . "\r\n" . + 'line message!', + $message->toString() + ); + } + + public function testChildrenCanBeAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'test foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testAttachmentsBeingAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + + public function testAttachmentsAndEmbeddedFilesBeingAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $message->attach($file); + + $cid = $file->getId(); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\2' . "\r\n" . + 'Content-Type: image/jpeg; name=myimage.jpg' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=myimage.jpg' . "\r\n" . + 'Content-ID: <' . $cid . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\2--' . "\r\n" . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + + public function testComplexEmbeddingOfContent() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $part = $this->_createMimePart(); + $part->setContentType('text/html'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo '); + + $message->attach($part); + + $cid = $file->getId(); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo ' . //=3D is just = in QP + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: image/jpeg; name=myimage.jpg' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=myimage.jpg' . "\r\n" . + 'Content-ID: <' . $cid . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + + public function testAttachingAndDetachingContent() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $message->attach($file); + + $cid = $file->getId(); + + $message->detach($attachment); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: image/jpeg; name=myimage.jpg' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=myimage.jpg' . "\r\n" . + 'Content-ID: <' . $cid . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString(), + '%s: Attachment should have been detached' + ); + } + + public function testBoundaryDoesNotAppearAfterAllPartsAreDetached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $message->detach($part1); + $message->detach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString(), + '%s: Message should be restored to orignal state after parts are detached' + ); + } + + public function testCharsetFormatOrDelSpAreNotShownWhenBoundaryIsSet() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setCharset('utf-8'); + $message->setFormat('flowed'); + $message->setDelSp(true); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'test foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testBodyCanBeSetWithAttachments() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setContentType('text/html'); + $message->setCharset('iso-8859-1'); + $message->setBody('foo'); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + base64_encode('') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testHtmlPartAlwaysAppearsLast() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/html'); + $part1->setBody('foo'); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/plain'); + $part2->setBody('bar'); + + $message->attach($part1); + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'bar' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testBodyBecomesPartIfOtherPartsAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setContentType('text/html'); + $message->setBody('foo'); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/plain'); + $part2->setBody('bar'); + + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'bar' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testBodyIsCanonicalized() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setBody( + 'just a test body' . "\n" . + 'with a new line' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'just a test body' . "\r\n" . + 'with a new line', + $message->toString() + ); + } + + // -- Private helpers + + protected function _createMessage() + { + return new Swift_Message(); + } + + protected function _createMimePart() + { + return new Swift_MimePart(); + } + + protected function _createAttachment() + { + return new Swift_Attachment(); + } + + protected function _createEmbeddedFile() + { + return new Swift_EmbeddedFile(); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/MimePartAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/MimePartAcceptanceTest.php new file mode 100644 index 0000000..50799dc --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/MimePartAcceptanceTest.php @@ -0,0 +1,16 @@ +register('properties.charset')->asValue(null); + return Swift_MimePart::newInstance(); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php new file mode 100644 index 0000000..ad0da9e --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php @@ -0,0 +1,124 @@ +_buffer = new Swift_Transport_StreamBuffer( + $this->_stub('Swift_ReplacementFilterFactory') + ); + } + + public function testReadLine() + { + $this->_initializeBuffer(); + + $line = $this->_buffer->readLine(0); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + $seq = $this->_buffer->write("QUIT\r\n"); + $this->assertTrue($seq); + $line = $this->_buffer->readLine($seq); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + $this->_buffer->terminate(); + } + + public function testWrite() + { + $this->_initializeBuffer(); + + $line = $this->_buffer->readLine(0); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + + $seq = $this->_buffer->write("HELO foo\r\n"); + $this->assertTrue($seq); + $line = $this->_buffer->readLine($seq); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + + $seq = $this->_buffer->write("QUIT\r\n"); + $this->assertTrue($seq); + $line = $this->_buffer->readLine($seq); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + $this->_buffer->terminate(); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + -> one($is2)->write('y') + ); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->write('x'); + $this->_buffer->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->flushBuffers() + -> one($is2)->flushBuffers() + ); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + ); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->write('x'); + + $this->_buffer->unbind($is2); + + $this->_buffer->write('y'); + } + + // -- Creation Methods + + private function _createMockInputStream() + { + return $this->_mock('Swift_InputByteStream'); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php new file mode 100644 index 0000000..b65e0ad --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php @@ -0,0 +1,33 @@ +skipUnless(SWIFT_SMTP_HOST, + 'Cannot run test without an SMTP host to connect to (define ' . + 'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_SMTP_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tcp', + 'blocking' => 1, + 'timeout' => 15 + )); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php new file mode 100644 index 0000000..b096adf --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php @@ -0,0 +1,25 @@ +skipIf(!SWIFT_SENDMAIL_PATH, + 'Cannot run test without a path to sendmail (define ' . + 'SWIFT_SENDMAIL_PATH in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS, + 'command' => SWIFT_SENDMAIL_PATH . ' -bs' + )); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php new file mode 100644 index 0000000..e252037 --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php @@ -0,0 +1,37 @@ +skipIf(!in_array('ssl', $streams), + 'SSL is not configured for your system. It is not possible to run this test' + ); + $this->skipIf(!SWIFT_SSL_HOST, + 'Cannot run test without an SSL enabled SMTP host to connect to (define ' . + 'SWIFT_SSL_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_SSL_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'ssl', + 'blocking' => 1, + 'timeout' => 15 + )); + } + +} diff --git a/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php new file mode 100644 index 0000000..9806bde --- /dev/null +++ b/lib/Swift/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php @@ -0,0 +1,37 @@ +skipIf(!in_array('tls', $streams), + 'TLS is not configured for your system. It is not possible to run this test' + ); + $this->skipIf(!SWIFT_TLS_HOST, + 'Cannot run test without a TLS enabled SMTP host to connect to (define ' . + 'SWIFT_TLS_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_TLS_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tls', + 'blocking' => 1, + 'timeout' => 15 + )); + } + +} diff --git a/lib/Swift/tests/bug/Swift/Bug118Test.php b/lib/Swift/tests/bug/Swift/Bug118Test.php new file mode 100644 index 0000000..1345e67 --- /dev/null +++ b/lib/Swift/tests/bug/Swift/Bug118Test.php @@ -0,0 +1,24 @@ +_message = new Swift_Message(); + } + + public function testCallingGenerateIdChangesTheMessageId() + { + $currentId = $this->_message->getId(); + $this->_message->generateId(); + $newId = $this->_message->getId(); + + $this->assertNotEqual($currentId, $newId); + } + +} diff --git a/lib/Swift/tests/bug/Swift/Bug34Test.php b/lib/Swift/tests/bug/Swift/Bug34Test.php new file mode 100644 index 0000000..6c32368 --- /dev/null +++ b/lib/Swift/tests/bug/Swift/Bug34Test.php @@ -0,0 +1,79 @@ +setCharset('utf-8'); + } + + public function testEmbeddedFilesWithMultipartDataCreateMultipartRelatedContentAsAnAlternative() + { + $message = Swift_Message::newInstance(); + $message->setCharset('utf-8'); + $message->setSubject('test subject'); + $message->addPart('plain part', 'text/plain'); + + $image = Swift_Image::newInstance('', 'image.gif', 'image/gif'); + $cid = $message->embed($image); + + $message->setBody('', 'text/html'); + + $message->setTo(array('user@domain.tld' => 'User')); + + $message->setFrom(array('other@domain.tld' => 'Other')); + $message->setSender(array('other@domain.tld' => 'Other')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $cidVal = $image->getId(); + + $this->assertPattern( + '~^' . + 'Sender: Other ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: Other ' . "\r\n" . + 'To: User ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'plain part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + '' . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=image.gif' . "\r\n" . + 'Content-ID: <' . $cidVal . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + +} diff --git a/lib/Swift/tests/bug/Swift/Bug35Test.php b/lib/Swift/tests/bug/Swift/Bug35Test.php new file mode 100644 index 0000000..eeef324 --- /dev/null +++ b/lib/Swift/tests/bug/Swift/Bug35Test.php @@ -0,0 +1,77 @@ +setCharset('utf-8'); + } + + public function testHTMLPartAppearsLastEvenWhenAttachmentsAdded() + { + $message = Swift_Message::newInstance(); + $message->setCharset('utf-8'); + $message->setSubject('test subject'); + $message->addPart('plain part', 'text/plain'); + + $attachment = Swift_Attachment::newInstance('', 'image.gif', 'image/gif'); + $message->attach($attachment); + + $message->setBody('HTML part', 'text/html'); + + $message->setTo(array('user@domain.tld' => 'User')); + + $message->setFrom(array('other@domain.tld' => 'Other')); + $message->setSender(array('other@domain.tld' => 'Other')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $this->assertPattern( + '~^' . + 'Sender: Other ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: Other ' . "\r\n" . + 'To: User ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'plain part' . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=image.gif' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + +} diff --git a/lib/Swift/tests/bug/Swift/Bug38Test.php b/lib/Swift/tests/bug/Swift/Bug38Test.php new file mode 100644 index 0000000..28fc6b8 --- /dev/null +++ b/lib/Swift/tests/bug/Swift/Bug38Test.php @@ -0,0 +1,199 @@ +_attFileName = 'data.txt'; + $this->_attFileType = 'text/plain'; + $this->_attFile = dirname(__FILE__) . '/../../_samples/files/data.txt'; + Swift_Preferences::getInstance()->setCharset('utf-8'); + } + + public function testWritingMessageToByteStreamProducesCorrectStructure() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $image = new Swift_Image('', 'image.gif', 'image/gif'); + + $cid = $message->embed($image); + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $imgId = $image->getId(); + + $stream = new Swift_ByteStream_ArrayByteStream(); + + $message->toByteStream($stream); + + $this->assertPatternInStream( + '~^' . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: user@domain.tld' . "\r\n" . + 'To: user@domain.tld' . "\r\n" . + 'Cc: other@domain.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=image.gif' . "\r\n" . + 'Content-ID: <' . preg_quote($imgId, '~') . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $stream + ); + } + + public function testWritingMessageToByteStreamTwiceProducesCorrectStructure() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $image = new Swift_Image('', 'image.gif', 'image/gif'); + + $cid = $message->embed($image); + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $imgId = $image->getId(); + + $pattern = '~^' . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: user@domain.tld' . "\r\n" . + 'To: user@domain.tld' . "\r\n" . + 'Cc: other@domain.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=image.gif' . "\r\n" . + 'Content-ID: <' . preg_quote($imgId, '~') . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D' + ; + + $streamA = new Swift_ByteStream_ArrayByteStream(); + $streamB = new Swift_ByteStream_ArrayByteStream(); + + $message->toByteStream($streamA); + $message->toByteStream($streamB); + + $this->assertPatternInStream($pattern, $streamA); + $this->assertPatternInStream($pattern, $streamB); + } + + public function testWritingMessageToByteStreamTwiceUsingAFileAttachment() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $attachment = Swift_Attachment::fromPath($this->_attFile); + + $message->attach($attachment); + + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $streamA = new Swift_ByteStream_ArrayByteStream(); + $streamB = new Swift_ByteStream_ArrayByteStream(); + + $pattern = '~^' . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: user@domain.tld' . "\r\n" . + 'To: user@domain.tld' . "\r\n" . + 'Cc: other@domain.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: ' . $this->_attFileType . '; name=' . $this->_attFileName . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=' . $this->_attFileName . "\r\n" . + "\r\n" . + preg_quote(base64_encode(file_get_contents($this->_attFile)), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D' + ; + + $message->toByteStream($streamA); + $message->toByteStream($streamB); + + $this->assertPatternInStream($pattern, $streamA); + $this->assertPatternInStream($pattern, $streamB); + } + + // -- Helpers + + public function assertPatternInStream($pattern, $stream, $message = '%s') + { + $string = ''; + while (false !== $bytes = $stream->read(8192)) + { + $string .= $bytes; + } + $this->assertPattern($pattern, $string, $message); + } + +} diff --git a/lib/Swift/tests/bug/Swift/Bug51Test.php b/lib/Swift/tests/bug/Swift/Bug51Test.php new file mode 100644 index 0000000..72bbbdb --- /dev/null +++ b/lib/Swift/tests/bug/Swift/Bug51Test.php @@ -0,0 +1,133 @@ +skipUnless( + is_writable(SWIFT_TMP_DIR), + '%s: This test requires tests/acceptance.conf.php to specify a ' . + 'writable SWIFT_TMP_DIR' + ); + } + + public function setUp() + { + $this->_attachmentFile = SWIFT_TMP_DIR . '/attach.rand.bin'; + file_put_contents($this->_attachmentFile, ''); + + $this->_outputFile = SWIFT_TMP_DIR . '/attach.out.bin'; + file_put_contents($this->_outputFile, ''); + } + + public function tearDown() + { + unlink($this->_attachmentFile); + unlink($this->_outputFile); + } + + public function testAttachmentsDoNotGetTruncatedUsingToByteStream() + { + //Run 100 times with 10KB attachments + for ($i = 0; $i < 10; ++$i) + { + $message = $this->_createMessageWithRandomAttachment( + 10000, $this->_attachmentFile + ); + + file_put_contents($this->_outputFile, ''); + $message->toByteStream( + new Swift_ByteStream_FileByteStream($this->_outputFile, true) + ); + + $emailSource = file_get_contents($this->_outputFile); + + $this->assertAttachmentFromSourceMatches( + file_get_contents($this->_attachmentFile), + $emailSource + ); + } + } + + public function testAttachmentsDoNotGetTruncatedUsingToString() + { + //Run 100 times with 10KB attachments + for ($i = 0; $i < 10; ++$i) + { + $message = $this->_createMessageWithRandomAttachment( + 10000, $this->_attachmentFile + ); + + $emailSource = $message->toString(); + + $this->assertAttachmentFromSourceMatches( + file_get_contents($this->_attachmentFile), + $emailSource + ); + } + } + + // -- Custom Assertions + + public function assertAttachmentFromSourceMatches($attachmentData, $source) + { + $encHeader = 'Content-Transfer-Encoding: base64'; + $base64declaration = strpos($source, $encHeader); + + $attachmentDataStart = strpos($source, "\r\n\r\n", $base64declaration); + $attachmentDataEnd = strpos($source, "\r\n--", $attachmentDataStart); + + if (false === $attachmentDataEnd) + { + $attachmentBase64 = trim(substr($source, $attachmentDataStart)); + } + else + { + $attachmentBase64 = trim(substr( + $source, $attachmentDataStart, + $attachmentDataEnd - $attachmentDataStart + )); + } + + $this->assertIdenticalBinary($attachmentData, base64_decode($attachmentBase64)); + } + + // -- Creation Methods + + private function _fillFileWithRandomBytes($byteCount, $file) + { + // I was going to use dd with if=/dev/random but this way seems more + // cross platform even if a hella expensive!! + + file_put_contents($file, ''); + $fp = fopen($file, 'wb'); + for ($i = 0; $i < $byteCount; ++$i) + { + $byteVal = rand(0, 255); + fwrite($fp, pack('i', $byteVal)); + } + fclose($fp); + } + + private function _createMessageWithRandomAttachment($size, $attachmentPath) + { + $this->_fillFileWithRandomBytes($size, $attachmentPath); + + $message = Swift_Message::newInstance() + ->setSubject('test') + ->setBody('test') + ->setFrom('a@b.c') + ->setTo('d@e.f') + ->attach(Swift_Attachment::fromPath($attachmentPath)) + ; + + return $message; + } + +} diff --git a/lib/Swift/tests/bug/Swift/Bug71Test.php b/lib/Swift/tests/bug/Swift/Bug71Test.php new file mode 100644 index 0000000..2dc0352 --- /dev/null +++ b/lib/Swift/tests/bug/Swift/Bug71Test.php @@ -0,0 +1,24 @@ +_message = new Swift_Message('test'); + } + + public function testCallingToStringAfterSettingNewBodyReflectsChanges() + { + $this->_message->setBody('BODY1'); + $this->assertPattern('/BODY1/', $this->_message->toString()); + + $this->_message->setBody('BODY2'); + $this->assertPattern('/BODY2/', $this->_message->toString()); + } + +} diff --git a/lib/Swift/tests/bug/Swift/Bug76Test.php b/lib/Swift/tests/bug/Swift/Bug76Test.php new file mode 100644 index 0000000..1d7e808 --- /dev/null +++ b/lib/Swift/tests/bug/Swift/Bug76Test.php @@ -0,0 +1,90 @@ +skipUnless( + is_writable(SWIFT_TMP_DIR), + '%s: This test requires tests/acceptance.conf.php to specify a ' . + 'writable SWIFT_TMP_DIR' + ); + } + + public function setUp() + { + $this->_inputFile = SWIFT_TMP_DIR . '/in.bin'; + file_put_contents($this->_inputFile, ''); + + $this->_outputFile = SWIFT_TMP_DIR . '/out.bin'; + file_put_contents($this->_outputFile, ''); + + $this->_encoder = $this->_createEncoder(); + } + + public function tearDown() + { + unlink($this->_inputFile); + unlink($this->_outputFile); + } + + public function testBase64EncodedLineLengthNeverExceeds76CharactersEvenIfArgsDo() + { + $this->_fillFileWithRandomBytes(1000, $this->_inputFile); + + $os = $this->_createStream($this->_inputFile); + $is = $this->_createStream($this->_outputFile); + + $this->_encoder->encodeByteStream($os, $is, 0, 80); //Exceeds 76 + + $this->assertMaxLineLength(76, $this->_outputFile, + '%s: Line length should not exceed 76 characters' + ); + } + + // -- Custom Assertions + + public function assertMaxLineLength($length, $filePath, $message = '%s') + { + $lines = file($filePath); + foreach ($lines as $line) + { + $this->assertTrue((strlen(trim($line)) <= 76), $message); + } + } + + // -- Creation Methods + + private function _fillFileWithRandomBytes($byteCount, $file) + { + // I was going to use dd with if=/dev/random but this way seems more + // cross platform even if a hella expensive!! + + file_put_contents($file, ''); + $fp = fopen($file, 'wb'); + for ($i = 0; $i < $byteCount; ++$i) + { + $byteVal = rand(0, 255); + fwrite($fp, pack('i', $byteVal)); + } + fclose($fp); + } + + private function _createEncoder() + { + return new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + private function _createStream($file) + { + return new Swift_ByteStream_FileByteStream($file, true); + } + +} diff --git a/lib/Swift/tests/helpers/Swift/Tests/IdenticalBinaryExpectation.php b/lib/Swift/tests/helpers/Swift/Tests/IdenticalBinaryExpectation.php new file mode 100644 index 0000000..d45dc8f --- /dev/null +++ b/lib/Swift/tests/helpers/Swift/Tests/IdenticalBinaryExpectation.php @@ -0,0 +1,84 @@ +_left = $left; + } + + /** + * Get the given string of bytes as a stirng of Hexadecimal sequences. + * @param string $binary + * @return string + */ + public function asHexString($binary) + { + $hex = ''; + + $bytes = unpack('H*', $binary); + + foreach ($bytes as &$byte) + { + $byte = strtoupper($byte); + } + + return implode('', $bytes); + } + + /** + * Test that the passed subject ($right) is identical to $left. + * @param string $right, subject + * @return boolean + */ + public function test($right) + { + $aHex = $this->asHexString($this->_left); + $bHex = $this->asHexString($right); + + return $aHex === $bHex; + } + + /** + * Get the message depending upon whether this expectation is satisfied. + * @param $right subject to compare against + * @return string + */ + public function testMessage($right) + { + if ($this->test($right)) + { + return 'Identical binary expectation [' . $this->asHexString($right) . ']'; + } + else + { + $this->_dumper=new SimpleDumper(); + return 'Identical binary expectation fails ' . + $this->_dumper->describeDifference( + $this->asHexString($this->_left), + $this->asHexString($right) + ); + } + } + +} diff --git a/lib/Swift/tests/helpers/Swift/Tests/SwiftSmokeTestCase.php b/lib/Swift/tests/helpers/Swift/Tests/SwiftSmokeTestCase.php new file mode 100644 index 0000000..76adb91 --- /dev/null +++ b/lib/Swift/tests/helpers/Swift/Tests/SwiftSmokeTestCase.php @@ -0,0 +1,55 @@ +skipUnless(SWIFT_SMOKE_TRANSPORT_TYPE, + '%s: Smoke tests are skipped if tests/smoke.conf.php is not editted' + ); + } + + protected function _getMailer() + { + switch (SWIFT_SMOKE_TRANSPORT_TYPE) + { + case 'smtp': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.smtp') + ->setHost(SWIFT_SMOKE_SMTP_HOST) + ->setPort(SWIFT_SMOKE_SMTP_PORT) + ->setUsername(SWIFT_SMOKE_SMTP_USER) + ->setPassword(SWIFT_SMOKE_SMTP_PASS) + ->setEncryption(SWIFT_SMOKE_SMTP_ENCRYPTION) + ; + break; + case 'sendmail': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.sendmail') + ->setCommand(SWIFT_SMOKE_SENDMAIL_COMMAND) + ; + break; + case 'mail': + case 'nativemail': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.mail'); + break; + default: + throw new Exception('Undefined transport [' . SWIFT_SMOKE_TRANSPORT_TYPE . ']'); + } + return new Swift_Mailer($transport); + } + + protected function _visualCheck($url) + { + $this->dump('{image @ ' . $url . '}'); + } + +} diff --git a/lib/Swift/tests/helpers/Swift/Tests/SwiftUnitTestCase.php b/lib/Swift/tests/helpers/Swift/Tests/SwiftUnitTestCase.php new file mode 100644 index 0000000..c4fed87 --- /dev/null +++ b/lib/Swift/tests/helpers/Swift/Tests/SwiftUnitTestCase.php @@ -0,0 +1,103 @@ +_mockery()->assertIsSatisfied(); + } + catch (Yay_NotSatisfiedException $e) + { + $this->fail($e->getMessage()); + } + $this->_mockery = null; + return parent::after($method); + } + + /** + * Assert two binary strings are an exact match. + * @param string $a + * @param string $b + * @param string $s formatted message + */ + public function assertIdenticalBinary($a, $b, $s = '%s') + { + return $this->assert(new Swift_Tests_IdenticalBinaryExpectation($a), $b, $s); + } + + // -- Protected methods + + /** + * Returns a singleton-per-test method for Yay_Mockery. + * @return Yay_Mockery + */ + protected function _mockery() + { + if (!isset($this->_mockery)) + { + $this->_mockery = new Yay_Mockery(); + } + return $this->_mockery; + } + + /** + * Create a mock object. + * @param string $class + * @return Yay_Mock + */ + protected function _mock($class) + { + return $this->_mockery()->mock($class); + } + + /** + * Add mock expectations. + * @param Yay_Expectations $expectations + */ + protected function _checking($expectations) + { + return $this->_mockery()->checking($expectations); + } + + /** + * Create a mock object which does nothing. + * @param string $class + * @return Yay_Mock + */ + protected function _stub($class) + { + $stub = $this->_mockery()->mock($class); + $this->_mockery()->checking(Yay_Expectations::create() + -> ignoring($stub) + ); + return $stub; + } + + protected function _states($machineName) + { + return $this->_mockery()->states($machineName); + } + + protected function _sequence($sequenceName) + { + return $this->_mockery()->sequence($sequenceName); + } + +} diff --git a/lib/Swift/tests/smoke.conf.php b/lib/Swift/tests/smoke.conf.php new file mode 100644 index 0000000..2a75b7d --- /dev/null +++ b/lib/Swift/tests/smoke.conf.php @@ -0,0 +1,63 @@ +_attFile = dirname(__FILE__) . '/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance() + ->setSubject('[Swift Mailer] AttachmentSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('This message should contain an attached ZIP file (named "textfile.zip").' . PHP_EOL . + 'When unzipped, the archive should produce a text file which reads:' . PHP_EOL . + '"This is part of a Swift Mailer v4 smoke test."' + ) + ->attach(Swift_Attachment::fromPath($this->_attFile)) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/attachment.jpg'); + } + +} diff --git a/lib/Swift/tests/smoke/Swift/Smoke/BasicSmokeTest.php b/lib/Swift/tests/smoke/Swift/Smoke/BasicSmokeTest.php new file mode 100644 index 0000000..57d6c01 --- /dev/null +++ b/lib/Swift/tests/smoke/Swift/Smoke/BasicSmokeTest.php @@ -0,0 +1,26 @@ +_getMailer(); + $message = Swift_Message::newInstance() + ->setSubject('[Swift Mailer] BasicSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('One, two, three, four, five...' . PHP_EOL . + 'six, seven, eight...' + ) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/basic.jpg'); + } + +} diff --git a/lib/Swift/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php b/lib/Swift/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php new file mode 100644 index 0000000..9867cad --- /dev/null +++ b/lib/Swift/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php @@ -0,0 +1,32 @@ +_attFile = dirname(__FILE__) . '/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance('[Swift Mailer] HtmlWithAttachmentSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->attach(Swift_Attachment::fromPath($this->_attFile)) + ->setBody('

This HTML-formatted message should contain an attached ZIP file (named "textfile.zip").' . PHP_EOL . + 'When unzipped, the archive should produce a text file which reads:

' . PHP_EOL . + '

This is part of a Swift Mailer v4 smoke test.

', 'text/html' + ) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/attachment.jpg'); + } + +} diff --git a/lib/Swift/tests/smoke/Swift/Smoke/InternationalSmokeTest.php b/lib/Swift/tests/smoke/Swift/Smoke/InternationalSmokeTest.php new file mode 100644 index 0000000..fee9c0d --- /dev/null +++ b/lib/Swift/tests/smoke/Swift/Smoke/InternationalSmokeTest.php @@ -0,0 +1,39 @@ +_attFile = dirname(__FILE__) . '/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance() + ->setCharset('utf-8') + ->setSubject('[Swift Mailer] InternationalSmokeTest (διεθνής)') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'ΧÏιστοφοÏου (Swift Mailer)')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('This message should contain an attached ZIP file (named "κείμενο, εδάφιο, θέμα.zip").' . PHP_EOL . + 'When unzipped, the archive should produce a text file which reads:' . PHP_EOL . + '"This is part of a Swift Mailer v4 smoke test."' . PHP_EOL . + PHP_EOL . + 'Following is some arbitrary Greek text:' . PHP_EOL . + 'Δεν βÏέθηκαν λέξεις.' + ) + ->attach(Swift_Attachment::fromPath($this->_attFile) + ->setContentType('application/zip') + ->setFilename('κείμενο, εδάφιο, θέμα.zip') + ) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/international.jpg'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php b/lib/Swift/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php new file mode 100644 index 0000000..046e972 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php @@ -0,0 +1,211 @@ +_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(1)) + { + $output[] = $bytes; + } + $this->assertEqual($input, $output, + '%s: Bytes read from stream should be the same as bytes in constructor' + ); + } + + public function testReadingMultipleBytesFromBaseInput() + { + $input = array('a', 'b', 'c', 'd'); + $bs = $this->_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(2)) + { + $output[] = $bytes; + } + $this->assertEqual(array('ab', 'cd'), $output, + '%s: Bytes read from stream should be in pairs' + ); + } + + public function testReadingOddOffsetOnLastByte() + { + $input = array('a', 'b', 'c', 'd', 'e'); + $bs = $this->_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(2)) + { + $output[] = $bytes; + } + $this->assertEqual(array('ab', 'cd', 'e'), $output, + '%s: Bytes read from stream should be in pairs except final read' + ); + } + + public function testSettingPointerPartway() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + $bs->setReadPointer(1); + $this->assertEqual('b', $bs->read(1), + '%s: Byte should be second byte since pointer as at offset 1' + ); + } + + public function testResettingPointerAfterExhaustion() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + while (false !== $bs->read(1)); + + $bs->setReadPointer(0); + $this->assertEqual('a', $bs->read(1), + '%s: Byte should be first byte since pointer as at offset 0' + ); + } + + public function testPointerNeverSetsBelowZero() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->setReadPointer(-1); + $this->assertEqual('a', $bs->read(1), + '%s: Byte should be first byte since pointer should be at offset 0' + ); + } + + public function testPointerNeverSetsAboveStackSize() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->setReadPointer(3); + $this->assertIdentical(false, $bs->read(1), + '%s: Stream should be at end and thus return false' + ); + } + + public function testBytesCanBeWrittenToStream() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->write('de'); + + $output = array(); + while (false !== $bytes = $bs->read(1)) + { + $output[] = $bytes; + } + $this->assertEqual(array('a', 'b', 'c', 'd', 'e'), $output, + '%s: Bytes read from stream should be from initial stack + written' + ); + } + + public function testContentsCanBeFlushed() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->flushBuffers(); + + $this->assertIdentical(false, $bs->read(1), + '%s: Contents have been flushed so read() should return false' + ); + } + + public function testConstructorCanTakeStringArgument() + { + $bs = $this->_createArrayStream('abc'); + $output = array(); + while (false !== $bytes = $bs->read(1)) + { + $output[] = $bytes; + } + $this->assertEqual(array('a', 'b', 'c'), $output, + '%s: Bytes read from stream should be the same as bytes in constructor' + ); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + -> one($is2)->write('y') + ); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->write('x'); + $bs->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->flushBuffers() + -> one($is2)->flushBuffers() + ); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + ); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->write('x'); + + $bs->unbind($is2); + + $bs->write('y'); + } + + // -- Creation Methods + + private function _createArrayStream($input) + { + return new Swift_ByteStream_ArrayByteStream($input); + } + + private function _createMockInputStream() + { + return $this->_mock('Swift_InputByteStream'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php b/lib/Swift/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php new file mode 100644 index 0000000..a4020ec --- /dev/null +++ b/lib/Swift/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php @@ -0,0 +1,48 @@ +assertIdentical(1, $reader->getInitialByteSize()); + + $reader = new Swift_CharacterReader_GenericFixedWidthReader(4); + $this->assertIdentical(4, $reader->getInitialByteSize()); + } + + public function testValidationValueIsBasedOnOctetCount() + { + $reader = new Swift_CharacterReader_GenericFixedWidthReader(4); + + $this->assertIdentical( + 1, $reader->validateByteSequence(array(0x01, 0x02, 0x03), 3) + ); //3 octets + + $this->assertIdentical( + 2, $reader->validateByteSequence(array(0x01, 0x0A), 2) + ); //2 octets + + $this->assertIdentical( + 3, $reader->validateByteSequence(array(0xFE), 1) + ); //1 octet + + $this->assertIdentical( + 0, $reader->validateByteSequence(array(0xFE, 0x03, 0x67, 0x9A), 4) + ); //All 4 octets + } + + public function testValidationFailsIfTooManyOctets() + { + $reader = new Swift_CharacterReader_GenericFixedWidthReader(6); + + $this->assertIdentical(-1, $reader->validateByteSequence( + array(0xFE, 0x03, 0x67, 0x9A, 0x10, 0x09, 0x85), 7 + )); //7 octets + } + +} diff --git a/lib/Swift/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php b/lib/Swift/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php new file mode 100644 index 0000000..381cb79 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php @@ -0,0 +1,64 @@ +read($size); ) + { + $c .= $bytes; + $size = $v->validateCharacter($c); + if (-1 == $size) + { + throw new Exception( ... invalid char .. ); + } + elseif (0 == $size) + { + return $c; //next character in $os + } + } + + */ + + private $_reader; + + public function setUp() + { + $this->_reader = new Swift_CharacterReader_UsAsciiReader(); + } + + public function testAllValidAsciiCharactersReturnZero() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) + { + $this->assertIdentical( + 0, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } + + public function testMultipleBytesAreInvalid() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; $ordinal += 2) + { + $this->assertIdentical( + -1, $this->_reader->validateByteSequence(array($ordinal, $ordinal + 1), 2) + ); + } + } + + public function testBytesAboveAsciiRangeAreInvalid() + { + for ($ordinal = 0x80; $ordinal <= 0xFF; ++$ordinal) + { + $this->assertIdentical( + -1, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } + +} diff --git a/lib/Swift/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php b/lib/Swift/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php new file mode 100644 index 0000000..6c7d40c --- /dev/null +++ b/lib/Swift/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php @@ -0,0 +1,76 @@ +_reader = new Swift_CharacterReader_Utf8Reader(); + } + + public function testLeading7BitOctetCausesReturnZero() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) + { + $this->assertIdentical( + 0, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } + + public function testLeadingByteOf2OctetCharCausesReturn1() + { + for ($octet = 0xC0; $octet <= 0xDF; ++$octet) + { + $this->assertIdentical( + 1, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf3OctetCharCausesReturn2() + { + for ($octet = 0xE0; $octet <= 0xEF; ++$octet) + { + $this->assertIdentical( + 2, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf4OctetCharCausesReturn3() + { + for ($octet = 0xF0; $octet <= 0xF7; ++$octet) + { + $this->assertIdentical( + 3, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf5OctetCharCausesReturn4() + { + for ($octet = 0xF8; $octet <= 0xFB; ++$octet) + { + $this->assertIdentical( + 4, $this->_reader->validateByteSequence(array($octet),1) + ); + } + } + + public function testLeadingByteOf6OctetCharCausesReturn5() + { + for ($octet = 0xFC; $octet <= 0xFD; ++$octet) + { + $this->assertIdentical( + 5, $this->_reader->validateByteSequence(array($octet),1) + ); + } + } + +} diff --git a/lib/Swift/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php b/lib/Swift/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php new file mode 100644 index 0000000..637209f --- /dev/null +++ b/lib/Swift/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php @@ -0,0 +1,376 @@ +_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', + 0xD0, 0x94, + 0xD0, 0xB6, + 0xD0, 0xBE, + 0xD1, 0x8D, + 0xD0, 0xBB, + 0xD0, 0xB0 + ) + ); + } + + public function testCharactersWrittenUseValidator() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + } + + public function testReadCharactersAreInTact() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + //String + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + //Stream + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + $this->assertIdenticalBinary( + pack('C*', 0xD0, 0xB6, 0xD0, 0xBE), $stream->read(2) + ); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1)); + $this->assertIdenticalBinary( + pack('C*', 0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->read(3) + ); + $this->assertIdenticalBinary(pack('C*', 0xD1, 0x85), $stream->read(1)); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testCharactersCanBeReadAsByteArrays() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + //String + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + //Stream + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + + $this->assertEqual(array(0xD0, 0x94), $stream->readBytes(1)); + $this->assertEqual(array(0xD0, 0xB6, 0xD0, 0xBE), $stream->readBytes(2)); + $this->assertEqual(array(0xD0, 0xBB), $stream->readBytes(1)); + $this->assertEqual( + array(0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->readBytes(3) + ); + $this->assertEqual(array(0xD1, 0x85), $stream->readBytes(1)); + + $this->assertIdentical(false, $stream->readBytes(1)); + } + + public function testRequestingLargeCharCountPastEndOfStream() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE), + $stream->read(100) + ); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testRequestingByteArrayCountPastEndOfStream() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertEqual(array(0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE), + $stream->readBytes(100) + ); + + $this->assertIdentical(false, $stream->readBytes(1)); + } + + public function testPointerOffsetCanBeSet() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + + $stream->setPointer(0); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + + $stream->setPointer(2); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1)); + } + + public function testContentsCanBeFlushed() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->flushContents(); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testByteStreamCanBeImportingUsesValidator() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + $os = $this->_getByteStream(); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-stream'); + $this->_checking(Expectations::create() + -> between(0,1)->of($os)->setReadPointer(0) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0x94)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xB6)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xBE)) + -> ignoring($os)->read(any()) -> returns(false) + ); + + $seq = $this->_mockery()->sequence('read-chars'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importByteStream($os); + } + + public function testImportingStreamProducesCorrectCharArray() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + $os = $this->_getByteStream(); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-stream'); + $this->_checking(Expectations::create() + -> between(0,1)->of($os)->setReadPointer(0) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0x94)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xB6)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xBE)) + -> ignoring($os)->read(any()) -> returns(false) + ); + + $seq = $this->_mockery()->sequence('read-chars'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importByteStream($os); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB6), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1)); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testAlgorithmWithFixedWidthCharsets() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $seq = $this->_mockery()->sequence('read-chars'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(2) + -> one($reader)->validateByteSequence(array(0xD1, 0x8D), 2) -> inSequence($seq) + -> one($reader)->validateByteSequence(array(0xD0, 0xBB), 2) -> inSequence($seq) + -> one($reader)->validateByteSequence(array(0xD0, 0xB0), 2) -> inSequence($seq) + ); + + $stream = new Swift_CharacterStream_ArrayCharacterStream( + $factory, 'utf-8' + ); + $stream->importString(pack('C*', 0xD1, 0x8D, 0xD0, 0xBB, 0xD0, 0xB0)); + + $this->assertIdenticalBinary(pack('C*', 0xD1, 0x8D), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB0), $stream->read(1)); + + $this->assertIdentical(false, $stream->read(1)); + } + + // -- Creation methods + + private function _getReader() + { + return $this->_mock('Swift_CharacterReader'); + } + + private function _getFactory($reader) + { + $factory = $this->_mock('Swift_CharacterReaderFactory'); + $this->_checking(Expectations::create() + -> allowing($factory)->getReaderFor('utf-8') -> returns($reader) + ); + return $factory; + } + + private function _getByteStream() + { + return $this->_mock('Swift_OutputByteStream'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/DependencyContainerTest.php b/lib/Swift/tests/unit/Swift/DependencyContainerTest.php new file mode 100644 index 0000000..b4c702b --- /dev/null +++ b/lib/Swift/tests/unit/Swift/DependencyContainerTest.php @@ -0,0 +1,177 @@ +arg1 = $arg1; + $this->arg2 = $arg2; + } +} + +class Swift_DependencyContainerTest extends Swift_Tests_SwiftUnitTestCase +{ + + private $_container; + + public function setUp() + { + $this->_container = new Swift_DependencyContainer(); + } + + public function testRegisterAndLookupValue() + { + $this->_container->register('foo')->asValue('bar'); + $this->assertIdentical('bar', $this->_container->lookup('foo')); + } + + public function testHasReturnsTrueForRegisteredValue() + { + $this->_container->register('foo')->asValue('bar'); + $this->assertTrue($this->_container->has('foo')); + } + + public function testHasReturnsFalseForUnregisteredValue() + { + $this->assertFalse($this->_container->has('foo')); + } + + public function testRegisterAndLookupNewInstance() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $this->assertIsA($this->_container->lookup('one'), 'One'); + } + + public function testHasReturnsTrueForRegisteredInstance() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $this->assertTrue($this->_container->has('one')); + } + + public function testNewInstanceIsAlwaysNew() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $a = $this->_container->lookup('one'); + $b = $this->_container->lookup('one'); + $this->assertClone($a, $b); + } + + public function testRegisterAndLookupSharedInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $this->assertIsA($this->_container->lookup('one'), 'One'); + } + + public function testHasReturnsTrueForSharedInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $this->assertTrue($this->_container->has('one')); + } + + public function testMultipleSharedInstancesAreSameInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $a = $this->_container->lookup('one'); + $b = $this->_container->lookup('one'); + $this->assertSame($a, $b); + } + + public function testNewInstanceWithDependencies() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One') + ->withDependencies(array('foo')); + $obj = $this->_container->lookup('one'); + $this->assertIdentical('FOO', $obj->arg1); + } + + public function testNewInstanceWithMultipleDependencies() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asValue(42); + $this->_container->register('one')->asNewInstanceOf('One') + ->withDependencies(array('foo', 'bar')); + $obj = $this->_container->lookup('one'); + $this->assertIdentical('FOO', $obj->arg1); + $this->assertIdentical(42, $obj->arg2); + } + + public function testNewInstanceWithInjectedObjects() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array('one', 'foo')); + $obj = $this->_container->lookup('two'); + $this->assertClone($this->_container->lookup('one'), $obj->arg1); + $this->assertIdentical('FOO', $obj->arg2); + } + + public function testNewInstanceWithAddConstructorValue() + { + $this->_container->register('one')->asNewInstanceOf('One') + ->addConstructorValue('x') + ->addConstructorValue(99); + $obj = $this->_container->lookup('one'); + $this->assertIdentical('x', $obj->arg1); + $this->assertIdentical(99, $obj->arg2); + } + + public function testNewInstanceWithAddConstructorLookup() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asValue(42); + $this->_container->register('one')->asNewInstanceOf('One') + ->addConstructorLookup('foo') + ->addConstructorLookup('bar'); + + $obj = $this->_container->lookup('one'); + $this->assertIdentical('FOO', $obj->arg1); + $this->assertIdentical(42, $obj->arg2); + } + + public function testResolvedDependenciesCanBeLookedUp() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array('one', 'foo')); + $deps = $this->_container->createDependenciesFor('two'); + $this->assertEqual( + array($this->_container->lookup('one'), 'FOO'), $deps + ); + } + + public function testArrayOfDependenciesCanBeSpecified() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array(array('one', 'foo'), 'foo')); + + $obj = $this->_container->lookup('two'); + $this->assertEqual(array($this->_container->lookup('one'), 'FOO'), $obj->arg1); + $this->assertIdentical('FOO', $obj->arg2); + } + + public function testAliasCanBeSet() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asAliasOf('foo'); + + $this->assertIdentical('FOO', $this->_container->lookup('bar')); + } + + public function testAliasOfAliasCanBeSet() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asAliasOf('foo'); + $this->_container->register('zip')->asAliasOf('bar'); + $this->_container->register('button')->asAliasOf('zip'); + + $this->assertIdentical('FOO', $this->_container->lookup('button')); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Encoder/Base64EncoderTest.php b/lib/Swift/tests/unit/Swift/Encoder/Base64EncoderTest.php new file mode 100644 index 0000000..92c9eff --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Encoder/Base64EncoderTest.php @@ -0,0 +1,180 @@ +_encoder = new Swift_Encoder_Base64Encoder(); + } + + /* + There's really no point in testing the entire base64 encoding to the + level QP encoding has been tested. base64_encode() has been in PHP for + years. + */ + + public function testInputOutputRatioIs3to4Bytes() + { + /* + RFC 2045, 6.8 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + */ + + $this->assertEqual( + 'MTIz', $this->_encoder->encodeString('123'), + '%s: 3 bytes of input should yield 4 bytes of output' + ); + $this->assertEqual( + 'MTIzNDU2', $this->_encoder->encodeString('123456'), + '%s: 6 bytes in input should yield 8 bytes of output' + ); + $this->assertEqual( + 'MTIzNDU2Nzg5', $this->_encoder->encodeString('123456789'), + '%s: 9 bytes in input should yield 12 bytes of output' + ); + } + + public function testPadLength() + { + /* + RFC 2045, 6.8 + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + + for ($i = 0; $i < 30; ++$i) + { + $input = pack('C', rand(0, 255)); + $this->assertPattern( + '~^[a-zA-Z0-9/\+]{2}==$~', $this->_encoder->encodeString($input), + '%s: A single byte should have 2 bytes of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) + { + $input = pack('C*', rand(0, 255), rand(0, 255)); + $this->assertPattern( + '~^[a-zA-Z0-9/\+]{3}=$~', $this->_encoder->encodeString($input), + '%s: Two bytes should have 1 byte of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) + { + $input = pack('C*', rand(0, 255), rand(0, 255), rand(0, 255)); + $this->assertPattern( + '~^[a-zA-Z0-9/\+]{4}$~', $this->_encoder->encodeString($input), + '%s: Three bytes should have no padding' + ); + } + } + + public function testMaximumLineLengthIs76Characters() + { + /* + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. + */ + + $input = + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk' . //38 + 'NERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEyMzQ1' . "\r\n" . //76 * + 'Njc4OTBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3' . //38 + 'h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla' . "\r\n" . //76 * + 'MTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BRUl' . //38 + 'NUVVZXWFla'; //48 + + $this->assertEqual( + $output, $this->_encoder->encodeString($input), + '%s: Lines should be no more than 76 characters' + ); + } + + public function testMaximumLineLengthCanBeSpecified() + { + $input = + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk' . //38 + 'NERUZHSElKS0' . "\r\n" . //50 * + 'xNTk9QUVJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNk' . //38 + 'ZWZnaGlqa2xt' . "\r\n" . //50 * + 'bm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1' . //38 + 'BRUlNUVVZXWF' . "\r\n" . //50 * + 'laMTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BR' . //38 + 'UlNUVVZXWFla'; //50 * + + $this->assertEqual( + $output, $this->_encoder->encodeString($input, 0, 50), + '%s: Lines should be no more than 100 characters' + ); + } + + public function testFirstLineLengthCanBeDifferent() + { + $input = + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk' . //38 + 'NERUZHSElKS0xNTk9QU' . "\r\n" . //57 * + 'VJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNkZWZnaGl' . //38 + 'qa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLT' . "\r\n" . //76 * + 'E1OT1BRUlNUVVZXWFlaMTIzNDU2Nzg5MEFCQ0R' . //38 + 'FRkdISUpLTE1OT1BRUlNUVVZXWFla'; //67 + + $this->assertEqual( + $output, $this->_encoder->encodeString($input, 19), + '%s: First line offset is 19 so first line should be 57 chars long' + ); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Encoder/QpEncoderTest.php b/lib/Swift/tests/unit/Swift/Encoder/QpEncoderTest.php new file mode 100644 index 0000000..0c93487 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Encoder/QpEncoderTest.php @@ -0,0 +1,372 @@ +_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array($ordinal)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertIdenticalBinary($char, $encoder->encodeString($char)); + } + } + + public function testWhiteSpaceAtLineEndingIsEncoded() + { + /* -- RFC 2045, 6.7 -- + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + */ + + $HT = chr(0x09); //9 + $SPACE = chr(0x20); //32 + + //HT + $string = 'a' . $HT . $HT . "\r\n" . 'b'; + + $seq = $this->_mockery()->sequence('byte-sequence'); + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($string) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x09)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x09)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual( + 'a' . $HT . '=09' . "\r\n" . 'b', + $encoder->encodeString($string) + ); + + //SPACE + $string = 'a' . $SPACE . $SPACE . "\r\n" . 'b'; + + $seq = $this->_mockery()->sequence('byte-sequence'); + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($string) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x20)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x20)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual( + 'a' . $SPACE . '=20' . "\r\n" . 'b', + $encoder->encodeString($string) + ); + } + + public function testCRLFIsLeftAlone() + { + /* + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + */ + + $string = 'a' . "\r\n" . 'b' . "\r\n" . 'c' . "\r\n"; + + $seq = $this->_mockery()->sequence('byte-sequence'); + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($string) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('c'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual($string, $encoder->encodeString($string)); + } + + public function testLinesLongerThan76CharactersAreSoftBroken() + { + /* + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + */ + + $input = str_repeat('a', 140); + + $charStream = $this->_createCharStream(); + $seq = $this->_mockery()->sequence('byte-sequence'); + + $exps = Expectations::create(); + + $exps -> one($charStream)->flushContents() + -> one($charStream)->importString($input) + ; + + $output = ''; + for ($i = 0; $i < 140; ++$i) + { + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))); + + if (75 == $i) + { + $output .= "=\r\n"; + } + $output .= 'a'; + } + + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false); + + $this->_checking($exps); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual($output, $encoder->encodeString($input)); + } + + public function testMaxLineLengthCanBeSpecified() + { + $input = str_repeat('a', 100); + + $charStream = $this->_createCharStream(); + $seq = $this->_mockery()->sequence('byte-sequence'); + + $exps = Expectations::create(); + + $exps -> one($charStream)->flushContents() + -> one($charStream)->importString($input) + ; + + $output = ''; + for ($i = 0; $i < 100; ++$i) + { + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))); + + if (53 == $i) + { + $output .= "=\r\n"; + } + $output .= 'a'; + } + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false); + + $this->_checking($exps); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual($output, $encoder->encodeString($input, 0, 54)); + } + + public function testBytesBelowPermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(0, 32) as $ordinal) + { + $char = chr($ordinal); + + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array($ordinal)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEqual( + sprintf('=%02X', $ordinal), $encoder->encodeString($char) + ); + } + } + + public function testDecimalByte61IsEncoded() + { + /* + According to Rule (1 & 2) + */ + + $char = '='; + + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array(61)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEqual('=3D', $encoder->encodeString('=')); + } + + public function testBytesAbovePermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(127, 255) as $ordinal) + { + $char = chr($ordinal); + + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array($ordinal)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEqual( + sprintf('=%02X', $ordinal), $encoder->encodeString($char) + ); + } + } + + public function testFirstLineLengthCanBeDifferent() + { + $input = str_repeat('a', 140); + + $charStream = $this->_createCharStream(); + $seq = $this->_mockery()->sequence('byte-sequence'); + + $exps = Expectations::create(); + + $exps -> one($charStream)->flushContents(); + $exps -> one($charStream)->importString($input); + + $output = ''; + for ($i = 0; $i < 140; ++$i) + { + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))); + + if (53 == $i || 53 + 75 == $i) + { + $output .= "=\r\n"; + } + $output .= 'a'; + } + + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false); + + $this->_checking($exps); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual( + $output, $encoder->encodeString($input, 22), + '%s: First line should start at offset 22 so can only have max length 54' + ); + } + + // -- Creation methods + + private function _createCharStream() + { + return $this->_mock('Swift_CharacterStream'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php b/lib/Swift/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php new file mode 100644 index 0000000..e141911 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php @@ -0,0 +1,152 @@ +_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + foreach (range(0x00, 0x7F) as $octet) + { + $char = pack('C', $octet); + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + $encoded = $encoder->encodeString($string); + + foreach (explode("\r\n", $encoded) as $line) + { + $this->assertPattern($this->_rfc2045Token, $line, + '%s: Encoder should always return a valid RFC 2045 token.'); + } + + + } + + public function testEncodingNonAsciiCharactersProducesValidToken() + { + $charStream = $this->_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + foreach (range(0x80, 0xFF) as $octet) + { + $char = pack('C', $octet); + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $encoded = $encoder->encodeString($string); + + foreach (explode("\r\n", $encoded) as $line) + { + $this->assertPattern($this->_rfc2045Token, $line, + '%s: Encoder should always return a valid RFC 2045 token.'); + } + + + } + + public function testMaximumLineLengthCanBeSet() + { + $charStream = $this->_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + for ($x = 0; $x < 200; ++$x) + { + $char = 'a'; + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $encoded = $encoder->encodeString($string, 0, 75); + + $this->assertEqual( + str_repeat('a', 75) . "\r\n" . + str_repeat('a', 75) . "\r\n" . + str_repeat('a', 50), + $encoded, + '%s: Lines should be wrapped at each 75 characters' + ); + + + } + + public function testFirstLineCanHaveShorterLength() + { + $charStream = $this->_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + for ($x = 0; $x < 200; ++$x) + { + $char = 'a'; + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + $encoded = $encoder->encodeString($string, 25, 75); + + $this->assertEqual( + str_repeat('a', 50) . "\r\n" . + str_repeat('a', 75) . "\r\n" . + str_repeat('a', 75), + $encoded, + '%s: First line should be 25 bytes shorter than the others.' + ); + + + } + +} diff --git a/lib/Swift/tests/unit/Swift/Events/CommandEventTest.php b/lib/Swift/tests/unit/Swift/Events/CommandEventTest.php new file mode 100644 index 0000000..1f809a6 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Events/CommandEventTest.php @@ -0,0 +1,43 @@ +_createEvent($this->_createTransport(), "FOO\r\n"); + $this->assertEqual("FOO\r\n", $evt->getCommand()); + } + + public function testSuccessCodesCanBeFetchedViaGetter() + { + $evt = $this->_createEvent($this->_createTransport(), "FOO\r\n", array(250)); + $this->assertEqual(array(250), $evt->getSuccessCodes()); + } + + public function testSourceIsBuffer() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, "FOO\r\n"); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, $command, + $successCodes = array()) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Events/EventObjectTest.php b/lib/Swift/tests/unit/Swift/Events/EventObjectTest.php new file mode 100644 index 0000000..7a9e4f4 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Events/EventObjectTest.php @@ -0,0 +1,39 @@ +_createEvent($source); + $ref = $evt->getSource(); + $this->assertReference($source, $ref); + } + + public function testEventDoesNotHaveCancelledBubbleWhenNew() + { + $source = new stdClass(); + $evt = $this->_createEvent($source); + $this->assertFalse($evt->bubbleCancelled()); + } + + public function testBubbleCanBeCancelledInEvent() + { + $source = new stdClass(); + $evt = $this->_createEvent($source); + $evt->cancelBubble(); + $this->assertTrue($evt->bubbleCancelled()); + } + + // -- Creation Methods + + private function _createEvent($source) + { + return new Swift_Events_EventObject($source); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Events/ResponseEventTest.php b/lib/Swift/tests/unit/Swift/Events/ResponseEventTest.php new file mode 100644 index 0000000..56fbbe5 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Events/ResponseEventTest.php @@ -0,0 +1,46 @@ +_createEvent($this->_createTransport(), "250 Ok\r\n", true); + $this->assertEqual("250 Ok\r\n", $evt->getResponse(), + '%s: Response should be available via getResponse()' + ); + } + + public function testResultCanBeFetchedViaGetter() + { + $evt = $this->_createEvent($this->_createTransport(), "250 Ok\r\n", false); + $this->assertFalse($evt->isValid(), + '%s: Result should be checkable via isValid()' + ); + } + + public function testSourceIsBuffer() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, "250 Ok\r\n", true); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, $response, $result) + { + return new Swift_Events_ResponseEvent($source, $response, $result); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Events/SendEventTest.php b/lib/Swift/tests/unit/Swift/Events/SendEventTest.php new file mode 100644 index 0000000..5c10883 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Events/SendEventTest.php @@ -0,0 +1,97 @@ +_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getMessage(); + $this->assertReference($message, $ref, + '%s: Message should be returned from getMessage()' + ); + } + + public function testTransportCanBeFetchViaGetter() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getTransport(); + $this->assertReference($transport, $ref, + '%s: Transport should be returned from getTransport()' + ); + } + + public function testTransportCanBeFetchViaGetSource() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getSource(); + $this->assertReference($transport, $ref, + '%s: Transport should be returned from getSource()' + ); + } + + public function testResultCanBeSetAndGet() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $evt->setResult( + Swift_Events_SendEvent::RESULT_SUCCESS | Swift_Events_SendEvent::RESULT_TENTATIVE + ); + + $this->assertTrue($evt->getResult() & Swift_Events_SendEvent::RESULT_SUCCESS); + $this->assertTrue($evt->getResult() & Swift_Events_SendEvent::RESULT_TENTATIVE); + } + + public function testFailedRecipientsCanBeSetAndGet() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $evt->setFailedRecipients(array('foo@bar', 'zip@button')); + + $this->assertEqual(array('foo@bar', 'zip@button'), $evt->getFailedRecipients(), + '%s: FailedRecipients should be returned from getter' + ); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, + Swift_Mime_Message $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } + + private function _createMessage() + { + return $this->_stub('Swift_Mime_Message'); + } + +} \ No newline at end of file diff --git a/lib/Swift/tests/unit/Swift/Events/SimpleEventDispatcherTest.php b/lib/Swift/tests/unit/Swift/Events/SimpleEventDispatcherTest.php new file mode 100644 index 0000000..2546f8b --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Events/SimpleEventDispatcherTest.php @@ -0,0 +1,168 @@ +_dispatcher = new Swift_Events_SimpleEventDispatcher(); + } + + public function testSendEventCanBeCreated() + { + $transport = $this->_stub('Swift_Transport'); + $message = $this->_stub('Swift_Mime_Message'); + $evt = $this->_dispatcher->createSendEvent($transport, $message); + $this->assertIsA($evt, 'Swift_Events_SendEvent'); + $this->assertSame($message, $evt->getMessage()); + $this->assertSame($transport, $evt->getTransport()); + } + + public function testCommandEventCanBeCreated() + { + $buf = $this->_stub('Swift_Transport'); + $evt = $this->_dispatcher->createCommandEvent($buf, "FOO\r\n", array(250)); + $this->assertIsA($evt, 'Swift_Events_CommandEvent'); + $this->assertSame($buf, $evt->getSource()); + $this->assertEqual("FOO\r\n", $evt->getCommand()); + $this->assertEqual(array(250), $evt->getSuccessCodes()); + } + + public function testResponseEventCanBeCreated() + { + $buf = $this->_stub('Swift_Transport'); + $evt = $this->_dispatcher->createResponseEvent($buf, "250 Ok\r\n", true); + $this->assertIsA($evt, 'Swift_Events_ResponseEvent'); + $this->assertSame($buf, $evt->getSource()); + $this->assertEqual("250 Ok\r\n", $evt->getResponse()); + $this->assertTrue($evt->isValid()); + } + + public function testTransportChangeEventCanBeCreated() + { + $transport = $this->_stub('Swift_Transport'); + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + $this->assertIsA($evt, 'Swift_Events_TransportChangeEvent'); + $this->assertSame($transport, $evt->getSource()); + } + + public function testTransportExceptionEventCanBeCreated() + { + $transport = $this->_stub('Swift_Transport'); + $ex = new Swift_TransportException(''); + $evt = $this->_dispatcher->createTransportExceptionEvent($transport, $ex); + $this->assertIsA($evt, 'Swift_Events_TransportExceptionEvent'); + $this->assertSame($transport, $evt->getSource()); + $this->assertSame($ex, $evt->getException()); + } + + public function testListenersAreNotifiedOfDispatchedEvent() + { + $transport = $this->_stub('Swift_Transport'); + + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + + $listenerA = $this->_mock('Swift_Events_TransportChangeListener'); + $listenerB = $this->_mock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($listenerA); + $this->_dispatcher->bindEventListener($listenerB); + + $this->_checking(Expectations::create() + -> one($listenerA)->transportStarted($evt) + -> one($listenerB)->transportStarted($evt) + ); + + $this->_dispatcher->dispatchEvent($evt, 'transportStarted'); + } + + public function testListenersAreOnlyCalledIfImplementingCorrectInterface() + { + $transport = $this->_stub('Swift_Transport'); + $message = $this->_stub('Swift_Mime_Message'); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $targetListener = $this->_mock('Swift_Events_SendListener'); + $otherListener = $this->_mock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($targetListener); + $this->_dispatcher->bindEventListener($otherListener); + + $this->_checking(Expectations::create() + -> one($targetListener)->sendPerformed($evt) + -> never($otherListener) + ); + + $this->_dispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + public function testListenersCanCancelBubblingOfEvent() + { + $transport = $this->_stub('Swift_Transport'); + $message = $this->_stub('Swift_Mime_Message'); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $listenerA = $this->_mock('Swift_Events_SendListener'); + $listenerB = $this->_mock('Swift_Events_SendListener'); + + $this->_dispatcher->bindEventListener($listenerA); + $this->_dispatcher->bindEventListener($listenerB); + + $this->_checking(Expectations::create() + -> one($listenerA)->sendPerformed($evt) -> calls(array($this, '_cancelBubble')) + -> never($listenerB) + ); + + $this->_dispatcher->dispatchEvent($evt, 'sendPerformed'); + + $this->assertTrue($evt->bubbleCancelled()); + } + + public function testAddingListenerTwiceDoesNotReceiveEventTwice() + { + $transport = $this->_stub('Swift_Transport'); + + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + + $listener = $this->_mock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($listener); + $this->_dispatcher->bindEventListener($listener); + + $this->_checking(Expectations::create() + -> one($listener)->transportStarted($evt) + -> never($listener)->transportStarted($evt) + ); + + $this->_dispatcher->dispatchEvent($evt, 'transportStarted'); + } + + // -- Mock callbacks + + public function _cancelBubble(Yay_Invocation $inv) + { + $args = $inv->getArguments(); + $args[0]->cancelBubble(true); + } + + // -- Private methods + + private function _createDispatcher(array $map) + { + $dispatcher = new Swift_Events_SimpleEventDispatcher($map); + return $dispatcher; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Events/TransportChangeEventTest.php b/lib/Swift/tests/unit/Swift/Events/TransportChangeEventTest.php new file mode 100644 index 0000000..fb5cbb8 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Events/TransportChangeEventTest.php @@ -0,0 +1,38 @@ +_createTransport(); + $evt = $this->_createEvent($transport); + $ref = $evt->getTransport(); + $this->assertReference($transport, $ref); + } + + public function testSourceIsTransport() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Events/TransportExceptionEventTest.php b/lib/Swift/tests/unit/Swift/Events/TransportExceptionEventTest.php new file mode 100644 index 0000000..f4943ef --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Events/TransportExceptionEventTest.php @@ -0,0 +1,51 @@ +_createException(); + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, $ex); + $ref = $evt->getException(); + $this->assertReference($ex, $ref, + '%s: Exception should be available via getException()' + ); + } + + public function testSourceIsTransport() + { + $ex = $this->_createException(); + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, $ex); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref, + '%s: Transport should be available via getSource()' + ); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $transport, + Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($transport, $ex); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } + + private function _createException() + { + return new Swift_TransportException(''); + } + +} diff --git a/lib/Swift/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php b/lib/Swift/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php new file mode 100644 index 0000000..f510e97 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php @@ -0,0 +1,244 @@ +_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEqual('whatever', $cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('testing', $cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEqual('test', $cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEqual('test', $cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = $this->_createOutputStream(); + $this->_checking(Expectations::create() + -> one($os)->read(optional()) -> returns('abc') + -> one($os)->read(optional()) -> returns('def') + -> one($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('abcdef', $cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = $this->_createOutputStream(); + $os2 = $this->_createOutputStream(); + $this->_checking(Expectations::create() + -> one($os1)->read(optional()) -> returns('abc') + -> one($os1)->read(optional()) -> returns('def') + -> one($os1)->read(optional()) -> returns(false) + -> ignoring($os1) + + -> one($os2)->read(optional()) -> returns('xyz') + -> one($os2)->read(optional()) -> returns('uvw') + -> one($os2)->read(optional()) -> returns(false) + -> ignoring($os2) + ); + $is = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($is); + + $cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('abcdefxyzuvw', $cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $os = $this->_createOutputStream(); + $this->_checking(Expectations::create() + -> one($os)->read(optional()) -> returns('abc') + -> one($os)->read(optional()) -> returns('def') + -> one($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + $is = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + $cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testabcdef', $cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + //See acceptance test for more detail + $is = $this->_createInputStream(); + $this->_checking(Expectations::create() + -> atLeast(1)->of($is)->write(any()) + -> ignoring($is) + ); + $kcis = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($kcis); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $cache->exportToByteStream($this->_key1, 'foo', $is); + } + + public function testKeyCanBeCleared() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + $cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($cache->hasKey($this->_key1, 'bar')); + $cache->clearAll($this->_key1); + $this->assertFalse($cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($cache->hasKey($this->_key1, 'bar')); + } + + // -- Creation methods + + private function _createCache($is) + { + return new Swift_KeyCache_ArrayKeyCache($is); + } + + private function _createKeyCacheInputStream($stub = false) + { + return $stub + ? $this->_stub('Swift_KeyCache_KeyCacheInputStream') + : $this->_mock('Swift_KeyCache_KeyCacheInputStream') + ; + } + + private function _createOutputStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } + +} diff --git a/lib/Swift/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php b/lib/Swift/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php new file mode 100644 index 0000000..2f6456f --- /dev/null +++ b/lib/Swift/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php @@ -0,0 +1,79 @@ +_createKeyCache(); + $this->_checking(Expectations::create() + -> one($cache)->setString($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString($this->_nsKey, 'foo', 'c', Swift_KeyCache::MODE_APPEND) + ); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->write('a'); + $stream->write('b'); + $stream->write('c'); + } + + public function testFlushContentClearsKey() + { + $cache = $this->_createKeyCache(); + $this->_checking(Expectations::create() + -> one($cache)->clearKey($this->_nsKey, 'foo') + ); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->flushBuffers(); + } + + public function testClonedStreamStillReferencesSameCache() + { + $cache = $this->_createKeyCache(); + $this->_checking(Expectations::create() + -> one($cache)->setString($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString('test', 'bar', 'x', Swift_KeyCache::MODE_APPEND) + ); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->write('a'); + $stream->write('b'); + + $newStream = clone $stream; + $newStream->setKeyCache($cache); + $newStream->setNsKey('test'); + $newStream->setItemKey('bar'); + + $newStream->write('x'); + } + + // -- Creation Methods + + private function _createKeyCache() + { + return $this->_mock('Swift_KeyCache'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php b/lib/Swift/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php new file mode 100644 index 0000000..ca2f41a --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php @@ -0,0 +1,48 @@ +assertFalse($it->hasNext()); + } + + public function testHasNextReturnsTrueIfItemsLeft() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertTrue($it->hasNext()); + } + + public function testReadingToEndOfListCausesHasNextToReturnFalse() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertTrue($it->hasNext()); + $it->nextRecipient(); + $this->assertFalse($it->hasNext()); + } + + public function testReturnedValueHasPreservedKeyValuePair() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertEqual(array('foo@bar' => 'Foo'), $it->nextRecipient()); + } + + public function testIteratorMovesNextAfterEachIteration() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array( + 'foo@bar' => 'Foo', + 'zip@button' => 'Zip thing', + 'test@test' => null + )); + $this->assertEqual(array('foo@bar' => 'Foo'), $it->nextRecipient()); + $this->assertEqual(array('zip@button' => 'Zip thing'), $it->nextRecipient()); + $this->assertEqual(array('test@test' => null), $it->nextRecipient()); + } + +} diff --git a/lib/Swift/tests/unit/Swift/MailerTest.php b/lib/Swift/tests/unit/Swift/MailerTest.php new file mode 100644 index 0000000..d134490 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/MailerTest.php @@ -0,0 +1,293 @@ +_createTransport(); + $message = $this->_createMessage(); + $con = $this->_states('Connection')->startsAs('off'); + $this->_checking(Expectations::create() + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('on')) + -> one($transport)->start() -> when($con->is('off')) -> then($con->is('on')) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->send($message); + } + + public function testTransportIsOnlyStartedOnce() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $con = $this->_states('Connection')->startsAs('off'); + $this->_checking(Expectations::create() + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('on')) + -> one($transport)->start() -> when($con->is('off')) -> then($con->is('on')) + -> ignoring($transport) + -> ignoring($message) + ); + $mailer = $this->_createMailer($transport); + for ($i = 0; $i < 10; ++$i) + { + $mailer->send($message); + } + } + + public function testMessageIsPassedToTransport() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($transport)->send($message, optional()) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->send($message); + } + + public function testBatchSendSendsOneMessagePerRecipient() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'one@domain.com' => 'One', + 'two@domain.com' => 'Two', + 'three@domain.com' => 'Three' + )) + -> exactly(3)->of($transport)->send($message, optional()) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->batchSend($message); + } + + public function testBatchSendChangesToFieldForEachRecipientBeforeRestoring() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'one@domain.com' => 'One', + 'two@domain.com' => 'Two', + 'three@domain.com' => 'Three' + )) + -> one($message)->setTo(array('one@domain.com' => 'One')) + -> one($message)->setTo(array('two@domain.com' => 'Two')) + -> one($message)->setTo(array('three@domain.com' => 'Three')) + -> one($message)->setTo(array( //Restore message + 'one@domain.com' => 'One', + 'two@domain.com' => 'Two', + 'three@domain.com' => 'Three' + )) + -> exactly(3)->of($transport)->send($message, optional()) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->batchSend($message); + } + + public function testBatchSendClearsAndRestoresCc() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('one@domain.com' => 'One')) + -> allowing($message)->getCc() -> returns(array('four@domain.com' => 'Four')) + -> one($message)->setCc(array()) + -> one($message)->setCc(array('four@domain.com' => 'Four')) + -> one($transport)->send($message, optional()) + -> never($transport)->send($message, optional()) //Only To: recipients in batch! + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->batchSend($message); + } + + public function testBatchSendClearsAndRestoresBcc() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('one@domain.com' => 'One')) + -> allowing($message)->getBcc() -> returns(array('four@domain.com' => 'Four')) + -> one($message)->setBcc(array()) + -> one($message)->setBcc(array('four@domain.com' => 'Four')) + -> one($transport)->send($message, optional()) + -> never($transport)->send($message, optional()) //Only To: recipients in batch! + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->batchSend($message); + } + + public function testSendReturnsCountFromTransport() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($transport)->send($message, optional()) -> returns(57) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $this->assertEqual(57, $mailer->send($message)); + } + + public function testBatchSendReturnCummulativeSendCount() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'one@domain.com' => 'One', + 'two@domain.com' => 'Two', + 'three@domain.com' => 'Three' + )) + -> one($message)->setTo(array('one@domain.com' => 'One')) + -> one($message)->setTo(array('two@domain.com' => 'Two')) + -> one($message)->setTo(array('three@domain.com' => 'Three')) + -> one($message)->setTo(array( //Restore message + 'one@domain.com' => 'One', + 'two@domain.com' => 'Two', + 'three@domain.com' => 'Three' + )) + -> one($transport)->send($message, optional()) -> returns(1) + -> one($transport)->send($message, optional()) -> returns(0) + -> one($transport)->send($message, optional()) -> returns(1) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $this->assertEqual(2, $mailer->batchSend($message)); + } + + public function testFailedRecipientReferenceIsPassedToTransport() + { + $failures = array(); + + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($transport)->send($message, reference($failures)) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->send($message, $failures); + } + + public function testFailedRecipientReferenceIsPassedToTransportInBatch() + { + $failures = array(); + + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar' => 'Foo')) + -> one($transport)->send($message, reference($failures)) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->batchSend($message, $failures); + } + + public function testBatchSendCanReadFromIterator() + { + $it = $this->_createIterator(); + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $this->_checking(Expectations::create() + -> one($it)->hasNext() -> returns(true) + -> one($it)->nextRecipient() -> returns(array('one@domain.com' => 'One')) + -> one($it)->hasNext() -> returns(true) + -> one($it)->nextRecipient() -> returns(array('two@domain.com' => 'Two')) + -> one($it)->hasNext() -> returns(true) + -> one($it)->nextRecipient() -> returns(array('three@domain.com' => 'Three')) + -> allowing($it)->hasNext() -> returns(false) + + -> ignoring($message)->getTo() -> returns(array()) + -> one($message)->setTo(array('one@domain.com' => 'One')) + -> one($message)->setTo(array('two@domain.com' => 'Two')) + -> one($message)->setTo(array('three@domain.com' => 'Three')) + -> one($message)->setTo(array()) //Message restoration to original state + + -> exactly(3)->of($transport)->send($message) + + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->batchSend($message, $failures, $it); + } + + public function testRegisterPluginDelegatesToTransport() + { + $plugin = $this->_createPlugin(); + $transport = $this->_createTransport(); + $mailer = $this->_createMailer($transport); + + $this->_checking(Expectations::create() + -> one($transport)->registerPlugin($plugin) + ); + $mailer->registerPlugin($plugin); + } + + // -- Creation methods + + private function _createPlugin() + { + return $this->_mock('Swift_Events_EventListener'); + } + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createMessage() + { + return $this->_mock('Swift_Mime_Message'); + } + + private function _createIterator() + { + return $this->_mock('Swift_Mailer_RecipientIterator'); + } + + private function _createMailer(Swift_Transport $transport) + { + return new Swift_Mailer($transport); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/AbstractMimeEntityTest.php b/lib/Swift/tests/unit/Swift/Mime/AbstractMimeEntityTest.php new file mode 100644 index 0000000..8045b9e --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/AbstractMimeEntityTest.php @@ -0,0 +1,1110 @@ +_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertSame($headers, $entity->getHeaders()); + } + + public function testContentTypeIsReturnedFromHeader() + { + $ctype = $this->_createHeader('Content-Type', 'image/jpeg-test'); + $headers = $this->_createHeaderSet(array('Content-Type' => $ctype)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual('image/jpeg-test', $entity->getContentType()); + } + + public function testContentTypeIsSetInHeader() + { + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $headers = $this->_createHeaderSet(array('Content-Type' => $ctype)); + $this->_checking(Expectations::create() + -> one($ctype)->setFieldBodyModel('image/jpeg') + -> ignoring($ctype) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setContentType('image/jpeg'); + } + + public function testContentTypeHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Type', 'image/jpeg') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setContentType('image/jpeg'); + } + + public function testContentTypeCanBeSetViaSetBody() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Type', 'text/html') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody('foo', 'text/html'); + } + + public function testGetEncoderFromConstructor() + { + $encoder = $this->_createEncoder('base64'); + $entity = $this->_createEntity($this->_createHeaderSet(), $encoder, + $this->_createCache() + ); + $this->assertSame($encoder, $entity->getEncoder()); + } + + public function testSetAndGetEncoder() + { + $encoder = $this->_createEncoder('base64'); + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($encoder); + $this->assertSame($encoder, $entity->getEncoder()); + } + + public function testSettingEncoderUpdatesTransferEncoding() + { + $encoder = $this->_createEncoder('base64'); + $encoding = $this->_createHeader( + 'Content-Transfer-Encoding', '8bit', array(), false + ); + $headers = $this->_createHeaderSet(array( + 'Content-Transfer-Encoding' => $encoding + )); + $this->_checking(Expectations::create() + -> one($encoding)->setFieldBodyModel('base64') + -> ignoring($encoding) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($encoder); + } + + public function testSettingEncoderAddsEncodingHeaderIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('Content-Transfer-Encoding', 'something') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($this->_createEncoder('something')); + } + + public function testIdIsReturnedFromHeader() + { + /* -- RFC 2045, 7. + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field + */ + + $cid = $this->_createHeader('Content-ID', 'zip@button'); + $headers = $this->_createHeaderSet(array('Content-ID' => $cid)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual('zip@button', $entity->getId()); + } + + public function testIdIsSetInHeader() + { + $cid = $this->_createHeader('Content-ID', 'zip@button', array(), false); + $headers = $this->_createHeaderSet(array('Content-ID' => $cid)); + $this->_checking(Expectations::create() + -> one($cid)->setFieldBodyModel('foo@bar') + -> ignoring($cid) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setId('foo@bar'); + } + + public function testIdIsAutoGenerated() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertPattern('/^.*?@.*?$/D', $entity->getId()); + } + + public function testGenerateIdCreatesNewId() + { + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $id1 = $entity->generateId(); + $id2 = $entity->generateId(); + $this->assertNotEqual($id1, $id2); + } + + public function testGenerateIdSetsNewId() + { + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $id = $entity->generateId(); + $this->assertEqual($id, $entity->getId()); + } + + public function testDescriptionIsReadFromHeader() + { + /* -- RFC 2045, 8. + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. This header + field is always optional. + */ + + $desc = $this->_createHeader('Content-Description', 'something'); + $headers = $this->_createHeaderSet(array('Content-Description' => $desc)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual('something', $entity->getDescription()); + } + + public function testDescriptionIsSetInHeader() + { + $desc = $this->_createHeader('Content-Description', '', array(), false); + $headers = $this->_createHeaderSet(array('Content-Description' => $desc)); + $this->_checking(Expectations::create() + -> one($desc)->setFieldBodyModel('whatever') + -> ignoring($desc) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setDescription('whatever'); + } + + public function testDescriptionHeaderIsAddedIfNotPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('Content-Description', 'whatever') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setDescription('whatever'); + } + + public function testSetAndGetMaxLineLength() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setMaxLineLength(60); + $this->assertEqual(60, $entity->getMaxLineLength()); + } + + public function testEncoderIsUsedForStringGeneration() + { + $encoder = $this->_createEncoder('base64', false); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString('blah', optional()) + -> ignoring($encoder) + ); + $entity = $this->_createEntity($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $entity->setBody("blah"); + $entity->toString(); + } + + public function testMaxLineLengthIsProvidedWhenEncoding() + { + $encoder = $this->_createEncoder('base64', false); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString('blah', 0, 65) + -> ignoring($encoder) + ); + $entity = $this->_createEntity($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $entity->setBody("blah"); + $entity->setMaxLineLength(65); + $entity->toString(); + } + + public function testHeadersAppearInString() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" . + "X-MyHeader: foobar\r\n" + ) + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "X-MyHeader: foobar\r\n", + $entity->toString() + ); + } + + public function testSetAndGetBody() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBody("blah\r\nblah!"); + $this->assertEqual("blah\r\nblah!", $entity->getBody()); + } + + public function testBodyIsAppended() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody("blah\r\nblah!"); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "\r\n" . + "blah\r\nblah!", + $entity->toString() + ); + } + + public function testGetBodyReturnsStringFromByteStream() + { + $os = $this->_createOutputStream("byte stream string"); + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBody($os); + $this->assertEqual("byte stream string", $entity->getBody()); + } + + public function testByteStreamBodyIsAppended() + { + $headers = $this->_createHeaderSet(array(), false); + $os = $this->_createOutputStream("streamed"); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody($os); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "\r\n" . + "streamed", + $entity->toString() + ); + } + + public function testBoundaryCanBeRetrieved() + { + /* -- RFC 2046, 5.1.1. + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + */ + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertPattern( + '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D', + $entity->getBoundary() + ); + } + + public function testBoundaryNeverChanges() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $firstBoundary = $entity->getBoundary(); + for ($i = 0; $i < 10; $i++) + { + $this->assertEqual($firstBoundary, $entity->getBoundary()); + } + } + + public function testBoundaryCanBeSet() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBoundary('foobar'); + $this->assertEqual('foobar', $entity->getBoundary()); + } + + public function testAddingChildrenGeneratesBoundaryInHeaders() + { + $child = $this->_createChild(); + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('boundary', any()) + -> ignoring($cType) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType + )), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + + public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_MIXED; + $level > Swift_Mime_MimeEntity::LEVEL_TOP; $level /= 2) + { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('multipart/mixed') + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; + $level > Swift_Mime_MimeEntity::LEVEL_MIXED; $level /= 2) + { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('multipart/alternative') + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_RELATED; + $level > Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; $level /= 2) + { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('multipart/related') + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testHighestLevelChildDeterminesContentType() + { + $combinations = array( + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + Swift_Mime_MimeEntity::LEVEL_RELATED + ), + 'type' => 'multipart/mixed' + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_RELATED + ), + 'type' => 'multipart/mixed' + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE + ), + 'type' => 'multipart/mixed' + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + Swift_Mime_MimeEntity::LEVEL_RELATED + ), + 'type' => 'multipart/alternative' + ) + ); + + foreach ($combinations as $combination) + { + $children = array(); + foreach ($combination['levels'] as $level) + { + $children[] = $this->_createChild($level); + } + + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel($combination['type']) + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren($children); + } + } + + public function testChildrenAppearNestedInString() + { + /* -- RFC 2046, 5.1.1. + (excerpt too verbose to paste here) + */ + + $headers = $this->_createHeaderSet(array(), false); + + $child1 = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar" + ); + + $child2 = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/html\r\n" . + "\r\n" . + "foobar" + ); + + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" + ) + -> ignoring($headers) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($child1, $child2)); + + $this->assertEqual( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" . + "\r\n" . + "\r\n--xxx\r\n" . + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar\r\n" . + "\r\n--xxx\r\n" . + "Content-Type: text/html\r\n" . + "\r\n" . + "foobar\r\n" . + "\r\n--xxx--\r\n", + $entity->toString() + ); + } + + public function testMixingLevelsIsHierarchical() + { + $headers = $this->_createHeaderSet(array(), false); + $newHeaders = $this->_createHeaderSet(array(), false); + + $part = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar" + ); + + $attachment = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_MIXED, + "Content-Type: application/octet-stream\r\n" . + "\r\n" . + "data" + ); + + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" + ) + -> ignoring($headers)->newInstance() -> returns($newHeaders) + -> ignoring($headers) + -> ignoring($newHeaders)->toString() -> returns( + "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n" + ) + -> ignoring($newHeaders) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($part, $attachment)); + + $this->assertPattern( + "~^" . + "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n" . + "\r\n\r\n--(.*?)\r\n" . + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar" . + "\r\n\r\n--\\1--\r\n" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: application/octet-stream\r\n" . + "\r\n" . + "data" . + "\r\n\r\n--xxx--\r\n" . + "\$~", + $entity->toString() + ); + } + + public function testSettingEncoderNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $encoder = $this->_createEncoder('base64'); + + $this->_checking(Expectations::create() + -> one($child)->encoderChanged($encoder) + -> ignoring($child) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->setEncoder($encoder); + } + + public function testReceiptOfEncoderChangeNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $encoder = $this->_createEncoder('base64'); + + $this->_checking(Expectations::create() + -> one($child)->encoderChanged($encoder) + -> ignoring($child) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->encoderChanged($encoder); + } + + public function testReceiptOfCharsetChangeNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + + $this->_checking(Expectations::create() + -> one($child)->charsetChanged('windows-874') + -> ignoring($child) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->charsetChanged('windows-874'); + } + + public function testEntityIsWrittenToByteStream() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $is = $this->_createInputStream(false); + $this->_checking(Expectations::create() + -> atLeast(1)->of($is)->write(any()) + -> ignoring($is) + ); + + $entity->toByteStream($is); + } + + public function testEntityHeadersAreComittedToByteStream() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $is = $this->_createInputStream(false); + $this->_checking(Expectations::create() + -> atLeast(1)->of($is)->commit() + -> atLeast(1)->of($is)->write(any()) + -> ignoring($is) + ); + + $entity->toByteStream($is); + } + + public function testOrderingTextBeforeHtml() + { + $htmlChild = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/html\r\n" . + "\r\n" . + "HTML PART", + false + ); + $textChild = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n" . + "\r\n" . + "TEXT PART", + false + ); + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" + ) + -> ignoring($headers) + -> ignoring($htmlChild)->getContentType() -> returns('text/html') + -> ignoring($htmlChild) + -> ignoring($textChild)->getContentType() -> returns('text/plain') + -> ignoring($textChild) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($htmlChild, $textChild)); + + $this->assertEqual( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: text/plain\r\n" . + "\r\n" . + "TEXT PART" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: text/html\r\n" . + "\r\n" . + "HTML PART" . + "\r\n\r\n--xxx--\r\n", + $entity->toString() + ); + } + + public function testUnsettingChildrenRestoresContentType() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE); + + $s = $this->_mockery()->sequence('Type setting'); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('image/jpeg') -> inSequence($s) + -> one($cType)->setFieldBodyModel('multipart/alternative') -> inSequence($s) + -> one($cType)->setFieldBodyModel('image/jpeg') -> inSequence($s) + -> ignoring($cType) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType + )), + $this->_createEncoder(), $this->_createCache() + ); + + $entity->setContentType('image/jpeg'); + $entity->setChildren(array($child)); + $entity->setChildren(array()); + } + + public function testBodyIsReadFromCacheWhenUsingToStringIfPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(true) + -> one($cache)->getString(any(), 'body') -> returns("\r\ncache\r\ncache!") + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "\r\n" . + "cache\r\ncache!", + $entity->toString() + ); + } + + public function testBodyIsAddedToCacheWhenUsingToString() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(false) + -> one($cache)->setString(any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE) + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + } + + public function testBodyIsClearedFromCacheIfNewBodySet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setBody("new\r\nnew!"); + } + + public function testBodyIsNotClearedFromCacheIfSameBodySet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> never($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setBody("blah\r\nblah!"); + } + + public function testBodyIsClearedFromCacheIfNewEncoderSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $otherEncoder = $this->_createEncoder(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setEncoder($otherEncoder); + } + + public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent() + { + $is = $this->_createInputStream(); + $cache = $this->_createCache(false); + + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(true) + -> one($cache)->exportToByteStream(any(), 'body', $is) + -> ignoring($cache) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $cache + ); + $entity->setBody('foo'); + + $entity->toByteStream($is); + } + + public function testBodyIsAddedToCacheWhenUsingToByteStream() + { + $is = $this->_createInputStream(); + $cache = $this->_createCache(false); + + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(false) + //The input stream should be fetched for writing + // Proving that it's actually written to is possible, but extremely + // fragile. Best let the acceptance tests cover this aspect + -> one($cache)->getInputByteStream(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $cache + ); + $entity->setBody('foo'); + + $entity->toByteStream($is); + } + + public function testFluidInterface() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertSame($entity, + $entity + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ); + } + + // -- Private helpers + + abstract protected function _createEntity($headers, $encoder, $cache); + + protected function _createChild($level = null, $string = '', $stub = true) + { + $child = $this->_mock('Swift_Mime_MimeEntity'); + if (isset($level)) + { + $this->_checking(Expectations::create() + -> ignoring($child)->getNestingLevel() -> returns($level) + ); + } + $this->_checking(Expectations::create() + -> ignoring($child)->toString() -> returns($string) + ); + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($child) + ); + } + return $child; + } + + protected function _createEncoder($name = 'quoted-printable', $stub = true) + { + $encoder = $this->_mock('Swift_Mime_ContentEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($name) + ); + + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($encoder)->encodeString(any(), optional()) + -> calls(array($this, 'returnStringFromEncoder')) + -> ignoring($encoder) + ); + } + return $encoder; + } + + protected function _createCache($stub = true) + { + $cache = $this->_mock('Swift_KeyCache'); + + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($cache) + ); + } + return $cache; + } + + protected function _createHeaderSet($headers = array(), $stub = true) + { + $set = $this->_mock('Swift_Mime_HeaderSet'); + foreach ($headers as $key => $header) + { + $this->_checking(Expectations::create() + -> ignoring($set)->has($key) -> returns(true) + -> ignoring($set)->get($key) -> returns($header) + ); + } + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($set)->newInstance() -> returns($set) + -> ignoring($set) + ); + } + return $set; + } + + protected function _createHeader($name, $model = null, $params = array(), $stub = true) + { + $header = $this->_mock('Swift_Mime_ParameterizedHeader'); + $this->_checking(Expectations::create() + -> ignoring($header)->getFieldName() -> returns($name) + -> ignoring($header)->getFieldBodyModel() -> returns($model) + ); + foreach ($params as $key => $value) + { + $this->_checking(Expectations::create() + -> ignoring($header)->getParameter($key) -> returns($value) + ); + } + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($header) + ); + } + return $header; + } + + protected function _createOutputStream($data = null, $stub = true) + { + $os = $this->_mock('Swift_OutputByteStream'); + if (isset($data)) + { + $pos = $this->_mockery()->states('position')->startsAs('at beginning'); + $this->_checking(Expectations::create() + -> ignoring($os)->read(optional()) -> returns($data) + -> when($pos->isNot('at end')) -> then($pos->is('at end')) + + -> ignoring($os)->read(optional()) -> returns(false) + ); + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($os) + ); + } + } + return $os; + } + + protected function _createInputStream($stub = true) + { + $is = $this->_mock('Swift_InputByteStream'); + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($is) + ); + } + return $is; + } + + // -- Mock helpers + + public function returnStringFromEncoder(Yay_Invocation $invocation) + { + $args = $invocation->getArguments(); + return array_shift($args); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/AttachmentTest.php b/lib/Swift/tests/unit/Swift/Mime/AttachmentTest.php new file mode 100644 index 0000000..294c8af --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/AttachmentTest.php @@ -0,0 +1,299 @@ +_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_MIXED, $attachment->getNestingLevel() + ); + } + + public function testDispositionIsReturnedFromHeader() + { + /* -- RFC 2183, 2.1, 2.2. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment'); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('attachment', $attachment->getDisposition()); + } + + public function testDispositionIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array(), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setFieldBodyModel('inline') + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setDisposition('inline'); + } + + public function testDispositionIsAddedIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Disposition', 'inline') + -> ignoring($headers) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + $attachment->setDisposition('inline'); + } + + public function testDispositionIsAutoDefaultedToAttachment() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Disposition', 'attachment') + -> ignoring($headers) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testDefaultContentTypeInitializedToOctetStream() + { + $cType = $this->_createHeader('Content-Type', '', + array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('application/octet-stream') + -> ignoring($cType) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + } + + public function testFilenameIsReturnedFromHeader() + { + /* -- RFC 2183, 2.3. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt') + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('foo.txt', $attachment->getFilename()); + } + + public function testFilenameIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt'), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'bar.txt') + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFilename('bar.txt'); + } + + public function testSettingFilenameSetsNameInContentType() + { + /* + This is a legacy requirement which isn't covered by up-to-date RFCs. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('name', 'bar.txt') + -> ignoring($cType) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFilename('bar.txt'); + } + + public function testSizeIsReturnedFromHeader() + { + /* -- RFC 2183, 2.7. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('size'=>1234) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(1234, $attachment->getSize()); + } + + public function testSizeIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array(), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('size', 12345) + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setSize(12345); + } + + public function testFilnameCanBeReadFromFileStream() + { + $file = $this->_createFileStream('/bar/file.ext', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt'), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'file.ext') + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFile($file); + } + + public function testContentTypeCanBeSetViaSetFile() + { + $file = $this->_createFileStream('/bar/file.ext', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt'), false + ); + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $headers = $this->_createHeaderSet(array( + 'Content-Disposition' => $disposition, + 'Content-Type' => $ctype + )); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'file.ext') + -> one($ctype)->setFieldBodyModel('text/html') + -> ignoring($disposition) + -> ignoring($ctype) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + $attachment->setFile($file, 'text/html'); + } + + public function XtestContentTypeCanBeLookedUpFromCommonListIfNotProvided() + { + $file = $this->_createFileStream('/bar/file.zip', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.zip'), false + ); + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $headers = $this->_createHeaderSet(array( + 'Content-Disposition' => $disposition, + 'Content-Type' => $ctype + )); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'file.zip') + -> one($ctype)->setFieldBodyModel('application/zip') + -> ignoring($disposition) + -> ignoring($ctype) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache(), array('zip'=>'application/zip', 'txt'=>'text/plain') + ); + $attachment->setFile($file); + } + + public function testDataCanBeReadFromFile() + { + $file = $this->_createFileStream('/foo/file.ext', ''); + $attachment = $this->_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFile($file); + $this->assertEqual('', $attachment->getBody()); + } + + public function testFluidInterface() + { + $attachment = $this->_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertSame($attachment, + $attachment + ->setContentType('application/pdf') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my pdf') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setDisposition('inline') + ->setFilename('afile.txt') + ->setSize(123) + ->setFile($this->_createFileStream('foo.txt', '')) + ); + } + + // -- Private helpers + + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createAttachment($headers, $encoder, $cache); + } + + protected function _createAttachment($headers, $encoder, $cache, + $mimeTypes = array()) + { + return new Swift_Mime_Attachment($headers, $encoder, $cache, $mimeTypes); + } + + protected function _createFileStream($path, $data, $stub = true) + { + $file = $this->_mock('Swift_FileStream'); + $pos = $this->_mockery()->states('position')->startsAs('at start'); + $this->_checking(Expectations::create() + -> ignoring($file)->getPath() -> returns($path) + -> ignoring($file)->read(optional()) -> returns($data) + -> when($pos->isNot('at end')) -> then($pos->is('at end')) + -> ignoring($file)->read(optional()) -> returns(false) + ); + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($file) + ); + } + return $file; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php b/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php new file mode 100644 index 0000000..dab5e85 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php @@ -0,0 +1,298 @@ +getArguments(); + $this->content .= current($args); + } + public function describeTo(Yay_Description $description) { + $description->appendText(' gathers input;'); + } +} + +class Swift_Mime_ContentEncoder_Base64ContentEncoderTest + extends Swift_Tests_SwiftUnitTestCase +{ + + private $_encoder; + + public function setUp() + { + $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + public function testNameIsBase64() + { + $this->assertEqual('base64', $this->_encoder->getName()); + } + + /* + There's really no point in testing the entire base64 encoding to the + level QP encoding has been tested. base64_encode() has been in PHP for + years. + */ + + public function testInputOutputRatioIs3to4Bytes() + { + /* + RFC 2045, 6.8 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + */ + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('123') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertEqual('MTIz', $collection->content); + } + + public function testPadLength() + { + /* + RFC 2045, 6.8 + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + + for ($i = 0; $i < 30; ++$i) + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns(pack('C', rand(0, 255))) + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertPattern('~^[a-zA-Z0-9/\+]{2}==$~', $collection->content, + '%s: A single byte should have 2 bytes of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns(pack('C*', rand(0, 255), rand(0, 255))) + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertPattern('~^[a-zA-Z0-9/\+]{3}=$~', $collection->content, + '%s: Two bytes should have 1 byte of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns(pack('C*', rand(0, 255), rand(0, 255), rand(0, 255))) + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertPattern('~^[a-zA-Z0-9/\+]{4}$~', $collection->content, + '%s: Three bytes should have no padding' + ); + } + } + + public function testMaximumLineLengthIs76Characters() + { + /* + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. + */ + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n" . + "U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + public function testMaximumLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is, 0, 50); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3OD\r\n" . + "kwQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJj\r\n" . + "ZGVmZ2hpamts", + $collection->content + ); + } + + public function testMaximumLineLengthIsNeverMoreThan76Chars() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is, 0, 100); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n" . + "U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + public function testFirstLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is, 19); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDR\r\n" . + "EVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + // -- Private Methods + + private function _createOutputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php b/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php new file mode 100644 index 0000000..cfd2b91 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php @@ -0,0 +1,281 @@ +getArguments(); + $this->content .= current($args); + } + public function describeTo(Yay_Description $description) { + $description->appendText(' gathers input;'); + } +} + +class Swift_Mime_ContentEncoder_PlainContentEncoderTest + extends Swift_Tests_SwiftUnitTestCase +{ + + public function testNameCanBeSpecifiedInConstructor() + { + $encoder = $this->_getEncoder('7bit'); + $this->assertEqual('7bit', $encoder->getName()); + + $encoder = $this->_getEncoder('8bit'); + $this->assertEqual('8bit', $encoder->getName()); + } + + public function testNoOctetsAreModifiedInString() + { + $encoder = $this->_getEncoder('7bit'); + foreach (range(0x00, 0xFF) as $octet) + { + $byte = pack('C', $octet); + $this->assertIdenticalBinary($byte, $encoder->encodeString($byte)); + } + } + + public function testNoOctetsAreModifiedInByteStream() + { + $encoder = $this->_getEncoder('7bit'); + foreach (range(0x00, 0xFF) as $octet) + { + $byte = pack('C', $octet); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns($byte) + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertIdenticalBinary($byte, $collection->content); + } + } + + public function testLineLengthCanBeSpecified() + { + $encoder = $this->_getEncoder('7bit'); + + $chars = array(); + for ($i = 0; $i < 50; $i++) + { + $chars[] = 'a'; + } + $input = implode(' ', $chars); //99 chars long + + $this->assertEqual( + 'a a a a a a a a a a a a a a a a a a a a a a a a a ' . "\r\n" . //50 * + 'a a a a a a a a a a a a a a a a a a a a a a a a a', //99 + $encoder->encodeString($input, 0, 50), + '%s: Lines should be wrapped at 50 chars' + ); + } + + public function testLineLengthCanBeSpecifiedInByteStream() + { + $encoder = $this->_getEncoder('7bit'); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + ); + + for ($i = 0; $i < 50; $i++) + { + $this->_checking(Expectations::create() + -> one($os)->read(optional()) -> returns('a ') + ); + } + + $this->_checking(Expectations::create() + -> allowing($os)->read(optional()) -> returns(false) + ); + + $encoder->encodeByteStream($os, $is, 0, 50); + $this->assertEqual( + str_repeat('a ', 25) . "\r\n" . str_repeat('a ', 25), + $collection->content + ); + } + + public function testencodeStringGeneratesCorrectCrlf() + { + $encoder = $this->_getEncoder('7bit', true); + $this->assertEqual("a\r\nb", $encoder->encodeString("a\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\nb", $encoder->encodeString("a\nb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\n\r\nb", $encoder->encodeString("a\n\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\n\r\nb", $encoder->encodeString("a\r\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\n\r\nb", $encoder->encodeString("a\n\nb"), + '%s: Line endings should be standardized' + ); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_1() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\r") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_2() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\n") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_3() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\n\r") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\n\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_4() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\n\n") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\n\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_5() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\r\r") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\n\r\nb", $collection->content); + } + + // -- Private helpers + + private function _getEncoder($name, $canonical = false) + { + return new Swift_Mime_ContentEncoder_PlainContentEncoder($name, $canonical); + } + + private function _createOutputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php b/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php new file mode 100644 index 0000000..b059d0b --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php @@ -0,0 +1,482 @@ +getArguments(); + $this->content .= current($args); + } + public function describeTo(Yay_Description $description) { + $description->appendText(' gathers input;'); + } +} + +class Swift_Mime_ContentEncoder_QpContentEncoderTest + extends Swift_Tests_SwiftUnitTestCase +{ + + public function testNameIsQuotedPrintable() + { + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder( + $this->_createCharacterStream(true) + ); + $this->assertEqual('quoted-printable', $encoder->getName()); + } + + /* -- RFC 2045, 6.7 -- + (1) (General 8bit representation) Any octet, except a CR or + LF that is part of a CRLF line break of the canonical + (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit + hexadecimal representation of the octet's value. The + digits of the hexadecimal alphabet, for this purpose, + are "0123456789ABCDEF". Uppercase letters must be + used; lowercase letters are not allowed. Thus, for + example, the decimal value 12 (US-ASCII form feed) can + be represented by "=0C", and the decimal value 61 (US- + ASCII EQUAL SIGN) can be represented by "=3D". This + rule must be followed except when the following rules + allow an alternative encoding. + */ + + public function testPermittedCharactersAreNotEncoded() + { + /* -- RFC 2045, 6.7 -- + (2) (Literal representation) Octets with decimal values of + 33 through 60 inclusive, and 62 through 126, inclusive, + MAY be represented as the US-ASCII characters which + correspond to those octets (EXCLAMATION POINT through + LESS THAN, and GREATER THAN through TILDE, + respectively). + */ + + foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) + { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array($ordinal)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertIdenticalBinary($char, $collection->content); + } + } + + public function testLinearWhiteSpaceAtLineEndingIsEncoded() + { + /* -- RFC 2045, 6.7 -- + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + */ + + $HT = chr(0x09); //9 + $SPACE = chr(0x20); //32 + + //HT + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x09)) + -> one($charStream)->readBytes(any()) -> returns(array(0x09)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + + $this->assertEqual("a\t=09\r\nb", $collection->content); + + //SPACE + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + + $this->assertEqual("a =20\r\nb", $collection->content); + } + + public function testCRLFIsLeftAlone() + { + /* + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + */ + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('c'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\nb\r\nc\r\n", $collection->content); + } + + public function testLinesLongerThan76CharactersAreSoftBroken() + { + /* + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + */ + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + for ($seq = 0; $seq <= 140; ++$seq) + { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + } + $this->_checking(Expectations::create() + -> allowing($charStream)->readBytes(any()) -> returns(false) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(str_repeat('a', 75) . "=\r\n" . str_repeat('a', 66), $collection->content); + } + + public function testMaxLineLengthCanBeSpecified() + { + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + + for ($seq = 0; $seq <= 100; ++$seq) + { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + } + + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(false) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is, 0, 54); + $this->assertEqual(str_repeat('a', 53) . "=\r\n" . str_repeat('a', 48), $collection->content); + } + + public function testBytesBelowPermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(0, 32) as $ordinal) + { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> one($charStream)->readBytes(any()) -> returns(array($ordinal)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(sprintf('=%02X', $ordinal), $collection->content); + } + } + + public function testDecimalByte61IsEncoded() + { + /* + According to Rule (1 & 2) + */ + + $char = chr(61); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> one($charStream)->readBytes(any()) -> returns(array(61)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(sprintf('=%02X', 61), $collection->content); + } + + public function testBytesAbovePermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(127, 255) as $ordinal) + { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> one($charStream)->readBytes(any()) -> returns(array($ordinal)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(sprintf('=%02X', $ordinal), $collection->content); + } + } + + public function testFirstLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + for ($seq = 0; $seq <= 140; ++$seq) + { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + } + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(false) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is, 22); + $this->assertEqual( + str_repeat('a', 53) . "=\r\n" . str_repeat('a', 75) . "=\r\n" . str_repeat('a', 13), + $collection->content + ); + } + + public function testObserverInterfaceCanChangeCharset() + { + $stream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($stream)->setCharacterSet('windows-1252') + -> ignoring($stream) + ); + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($stream); + $encoder->charsetChanged('windows-1252'); + } + + // -- Creation Methods + + private function _createCharacterStream($stub = false) + { + return $stub + ? $this->_stub('Swift_CharacterStream') + : $this->_mock('Swift_CharacterStream') + ; + } + + private function _createEncoder($charStream) + { + return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream); + } + + private function _createOutputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/EmbeddedFileTest.php b/lib/Swift/tests/unit/Swift/Mime/EmbeddedFileTest.php new file mode 100644 index 0000000..15fe70f --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/EmbeddedFileTest.php @@ -0,0 +1,64 @@ +_createEmbeddedFile($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_RELATED, $file->getNestingLevel() + ); + } + + public function testIdIsAutoGenerated() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addIdHeader('Content-ID', pattern('/^.*?@.*?$/D')) + -> ignoring($headers) + ); + $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testDefaultDispositionIsAttachment() + { //Overridden + } + + public function testDefaultDispositionIsInline() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Disposition', 'inline') + -> ignoring($headers) + ); + $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + // -- Private helpers + + protected function _createAttachment($headers, $encoder, $cache, + $mimeTypes = array()) + { + return $this->_createEmbeddedFile($headers, $encoder, $cache, $mimeTypes); + } + + private function _createEmbeddedFile($headers, $encoder, $cache) + { + return new Swift_Mime_EmbeddedFile($headers, $encoder, $cache); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php b/lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php new file mode 100644 index 0000000..321219d --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php @@ -0,0 +1,17 @@ +assertEqual('B', $encoder->getName()); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php b/lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php new file mode 100644 index 0000000..3a63818 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php @@ -0,0 +1,231 @@ +_createEncoder( + $this->_createCharacterStream(true) + ); + $this->assertEqual('Q', $encoder->getName()); + } + + public function testSpaceAndTabNeverAppear() + { + /* -- RFC 2047, 4. + Only a subset of the printable ASCII characters may be used in + 'encoded-text'. Space and tab characters are not allowed, so that + the beginning and end of an 'encoded-word' are obvious. + */ + + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(0x09)) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertNoPattern('~[ \t]~', $encoder->encodeString("a \t b"), + '%s: encoded-words in headers cannot contain LWSP as per RFC 2047.' + ); + } + + public function testSpaceIsRepresentedByUnderscore() + { + /* -- RFC 2047, 4.2. + (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be + represented as "_" (underscore, ASCII 95.). (This character may + not pass through some internetwork mail gateways, but its use + will greatly enhance readability of "Q" encoded data with mail + readers that do not support this encoding.) Note that the "_" + always represents hexadecimal 20, even if the SPACE character + occupies a different code position in the character set in use. + */ + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual('a_b', $encoder->encodeString('a b'), + '%s: Spaces can be represented by more readable underscores as per RFC 2047.' + ); + } + + public function testEqualsAndQuestionAndUnderscoreAreEncoded() + { + /* -- RFC 2047, 4.2. + (3) 8-bit values which correspond to printable ASCII characters other + than "=", "?", and "_" (underscore), MAY be represented as those + characters. (But see section 5 for restrictions.) In + particular, SPACE and TAB MUST NOT be represented as themselves + within encoded words. + */ + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('='))) + -> one($charStream)->readBytes(any()) -> returns(array(ord('?'))) + -> one($charStream)->readBytes(any()) -> returns(array(ord('_'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual('=3D=3F=5F', $encoder->encodeString('=?_'), + '%s: Chars =, ? and _ (underscore) may not appear as per RFC 2047.' + ); + } + + public function testParensAndQuotesAreEncoded() + { + /* -- RFC 2047, 5 (2). + A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT + contain the characters "(", ")" or " + */ + + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('('))) + -> one($charStream)->readBytes(any()) -> returns(array(ord('"'))) + -> one($charStream)->readBytes(any()) -> returns(array(ord(')'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual('=28=22=29', $encoder->encodeString('(")'), + '%s: Chars (, " (DQUOTE) and ) may not appear as per RFC 2047.' + ); + } + + public function testOnlyCharactersAllowedInPhrasesAreUsed() + { + /* -- RFC 2047, 5. + (3) As a replacement for a 'word' entity within a 'phrase', for example, + one that precedes an address in a From, To, or Cc header. The ABNF + definition for 'phrase' from RFC 822 thus becomes: + + phrase = 1*( encoded-word / word ) + + In this case the set of characters that may be used in a "Q"-encoded + 'encoded-word' is restricted to: . An 'encoded-word' that appears within a + 'phrase' MUST be separated from any adjacent 'word', 'text' or + 'special' by 'linear-white-space'. + */ + + $allowedBytes = array_merge( + range(ord('a'), ord('z')), range(ord('A'), ord('Z')), + range(ord('0'), ord('9')), + array(ord('!'), ord('*'), ord('+'), ord('-'), ord('/')) + ); + + foreach (range(0x00, 0xFF) as $byte) + { + $char = pack('C', $byte); + + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array($byte)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $encodedChar = $encoder->encodeString($char); + + if (in_array($byte, $allowedBytes)) + { + $this->assertEqual($char, $encodedChar, + '%s: Character ' . $char . ' should not be encoded.' + ); + } + elseif (0x20 == $byte) //Special case + { + $this->assertEqual('_', $encodedChar, + '%s: Space character should be replaced.' + ); + } + else + { + $this->assertEqual(sprintf('=%02X', $byte), $encodedChar, + '%s: Byte ' . $byte . ' should be encoded.' + ); + } + } + } + + public function testEqualsNeverAppearsAtEndOfLine() + { + /* -- RFC 2047, 5 (3). + The 'encoded-text' in an 'encoded-word' must be self-contained; + 'encoded-text' MUST NOT be continued from one 'encoded-word' to + another. This implies that the 'encoded-text' portion of a "B" + 'encoded-word' will be a multiple of 4 characters long; for a "Q" + 'encoded-word', any "=" character that appears in the 'encoded-text' + portion will be followed by two hexadecimal characters. + */ + + $input = str_repeat('a', 140); + + $charStream = $this->_createCharacterStream(); + + $output = ''; + $seq = 0; + for (; $seq < 140; ++$seq) + { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + + if (75 == $seq) + { + $output .= "\r\n"; // =\r\n + } + $output .= 'a'; + } + + $this->_checking(Expectations::create() + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual($output, $encoder->encodeString($input)); + } + + // -- Creation Methods + + private function _createEncoder($charStream) + { + return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream); + } + + private function _createCharacterStream($stub = false) + { + return $stub + ? $this->_stub('Swift_CharacterStream') + : $this->_mock('Swift_CharacterStream') + ; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/Headers/DateHeaderTest.php b/lib/Swift/tests/unit/Swift/Mime/Headers/DateHeaderTest.php new file mode 100644 index 0000000..204e9fe --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/Headers/DateHeaderTest.php @@ -0,0 +1,77 @@ +_getHeader('Date'); + $this->assertEqual(Swift_Mime_Header::TYPE_DATE, $header->getFieldType()); + } + + public function testGetTimestamp() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertIdentical($timestamp, $header->getTimestamp()); + } + + public function testTimestampCanBeSetBySetter() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertIdentical($timestamp, $header->getTimestamp()); + } + + public function testIntegerTimestampIsConvertedToRfc2822Date() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEqual(date('r', $timestamp), $header->getFieldBody()); + } + + public function testSetBodyModel() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setFieldBodyModel($timestamp); + $this->assertEqual(date('r', $timestamp), $header->getFieldBody()); + } + + public function testGetBodyModel() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEqual($timestamp, $header->getFieldBodyModel()); + } + + public function testToString() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEqual('Date: ' . date('r', $timestamp) . "\r\n", + $header->toString() + ); + } + + // -- Private methods + + private function _getHeader($name) + { + return new Swift_Mime_Headers_DateHeader($name); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php b/lib/Swift/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php new file mode 100644 index 0000000..c367cb0 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php @@ -0,0 +1,209 @@ +_getHeader('Message-ID'); + $this->assertEqual(Swift_Mime_Header::TYPE_ID, $header->getFieldType()); + } + + public function testValueMatchesMsgIdSpec() + { + /* -- RFC 2822, 3.6.4. + message-id = "Message-ID:" msg-id CRLF + + in-reply-to = "In-Reply-To:" 1*msg-id CRLF + + references = "References:" 1*msg-id CRLF + + msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + + id-left = dot-atom-text / no-fold-quote / obs-id-left + + id-right = dot-atom-text / no-fold-literal / obs-id-right + + no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + + no-fold-literal = "[" *(dtext / quoted-pair) "]" + */ + + $header = $this->_getHeader('Message-ID'); + $header->setId('id-left@id-right'); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testIdCanBeRetreivedVerbatim() + { + $header = $this->_getHeader('Message-ID'); + $header->setId('id-left@id-right'); + $this->assertEqual('id-left@id-right', $header->getId()); + } + + public function testMultipleIdsCanBeSet() + { + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEqual(array('a@b', 'x@y'), $header->getIds()); + } + + public function testSettingMultipleIdsProducesAListValue() + { + /* -- RFC 2822, 3.6.4. + The "References:" and "In-Reply-To:" field each contain one or more + unique message identifiers, optionally separated by CFWS. + + .. SNIP .. + + in-reply-to = "In-Reply-To:" 1*msg-id CRLF + + references = "References:" 1*msg-id CRLF + */ + + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEqual(' ', $header->getFieldBody()); + } + + public function testIdLeftCanBeQuoted() + { + /* -- RFC 2822, 3.6.4. + id-left = dot-atom-text / no-fold-quote / obs-id-left + */ + + $header = $this->_getHeader('References'); + $header->setId('"ab"@c'); + $this->assertEqual('"ab"@c', $header->getId()); + $this->assertEqual('<"ab"@c>', $header->getFieldBody()); + } + + public function testIdLeftCanContainAnglesAsQuotedPairs() + { + /* -- RFC 2822, 3.6.4. + no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + */ + + $header = $this->_getHeader('References'); + $header->setId('"a\\<\\>b"@c'); + $this->assertEqual('"a\\<\\>b"@c', $header->getId()); + $this->assertEqual('<"a\\<\\>b"@c>', $header->getFieldBody()); + } + + public function testIdLeftCanBeDotAtom() + { + $header = $this->_getHeader('References'); + $header->setId('a.b+&%$.c@d'); + $this->assertEqual('a.b+&%$.c@d', $header->getId()); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testInvalidIdLeftThrowsException() + { + try + { + $header = $this->_getHeader('References'); + $header->setId('a b c@d'); + $this->fail( + 'Exception should be thrown since "a b c" is not valid id-left.' + ); + } + catch (Exception $e) + { + $this->pass(); + } + } + + public function testIdRightCanBeDotAtom() + { + /* -- RFC 2822, 3.6.4. + id-right = dot-atom-text / no-fold-literal / obs-id-right + */ + + $header = $this->_getHeader('References'); + $header->setId('a@b.c+&%$.d'); + $this->assertEqual('a@b.c+&%$.d', $header->getId()); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testIdRightCanBeLiteral() + { + /* -- RFC 2822, 3.6.4. + no-fold-literal = "[" *(dtext / quoted-pair) "]" + */ + + $header = $this->_getHeader('References'); + $header->setId('a@[1.2.3.4]'); + $this->assertEqual('a@[1.2.3.4]', $header->getId()); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testInvalidIdRightThrowsException() + { + try + { + $header = $this->_getHeader('References'); + $header->setId('a@b c d'); + $this->fail( + 'Exception should be thrown since "b c d" is not valid id-right.' + ); + } + catch (Exception $e) + { + $this->pass(); + } + } + + public function testMissingAtSignThrowsException() + { + /* -- RFC 2822, 3.6.4. + msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + */ + + try + { + $header = $this->_getHeader('References'); + $header->setId('abc'); + $this->fail( + 'Exception should be thrown since "abc" is does not contain @.' + ); + } + catch (Exception $e) + { + $this->pass(); + } + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Message-ID'); + $header->setFieldBodyModel('a@b'); + $this->assertEqual(array('a@b'), $header->getIds()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Message-ID'); + $header->setId('a@b'); + $this->assertEqual(array('a@b'), $header->getFieldBodyModel()); + } + + public function testStringValue() + { + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEqual('References: ' . "\r\n", $header->toString()); + } + + // -- Private methods + + private function _getHeader($name) + { + return new Swift_Mime_Headers_IdentificationHeader($name); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php b/lib/Swift/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php new file mode 100644 index 0000000..262fd23 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php @@ -0,0 +1,340 @@ +_getHeader('To', $this->_getEncoder('Q', true)); + $this->assertEqual(Swift_Mime_Header::TYPE_MAILBOX, $header->getFieldType()); + } + + public function testMailboxIsSetForAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses('chris@swiftmailer.org'); + $this->assertEqual(array('chris@swiftmailer.org'), + $header->getNameAddressStrings() + ); + } + + public function testMailboxIsRenderedForNameAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn')); + $this->assertEqual( + array('Chris Corbyn '), $header->getNameAddressStrings() + ); + } + + public function testAddressCanBeReturnedForAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses('chris@swiftmailer.org'); + $this->assertEqual(array('chris@swiftmailer.org'), $header->getAddresses()); + } + + public function testAddressCanBeReturnedForNameAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn')); + $this->assertEqual(array('chris@swiftmailer.org'), $header->getAddresses()); + } + + public function testQuotesInNameAreQuoted() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, "DHE"' + )); + $this->assertEqual( + array('"Chris Corbyn, \"DHE\"" '), + $header->getNameAddressStrings() + ); + } + + public function testEscapeCharsInNameAreQuoted() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, \\escaped\\' + )); + $this->assertEqual( + array('"Chris Corbyn, \\\\escaped\\\\" '), + $header->getNameAddressStrings() + ); + } + + public function testGetMailboxesReturnsNameValuePairs() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, DHE' + )); + $this->assertEqual( + array('chris@swiftmailer.org' => 'Chris Corbyn, DHE'), $header->getNameAddresses() + ); + } + + public function testMultipleAddressesCanBeSetAndFetched() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org' + )); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testMultipleAddressesAsMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org' + )); + $this->assertEqual( + array('chris@swiftmailer.org'=>null, 'mark@swiftmailer.org'=>null), + $header->getNameAddresses() + ); + } + + public function testMultipleAddressesAsMailboxStrings() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org' + )); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getNameAddressStrings() + ); + } + + public function testMultipleNamedMailboxesReturnsMultipleAddresses() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testMultipleNamedMailboxesReturnsMultipleMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + ), + $header->getNameAddresses() + ); + } + + public function testMultipleMailboxesProducesMultipleMailboxStrings() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual(array( + 'Chris Corbyn ', + 'Mark Corbyn ' + ), + $header->getNameAddressStrings() + ); + } + + public function testSetAddressesOverwritesAnyMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + array('chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn'), + $header->getNameAddresses() + ); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + + $header->setAddresses(array('chris@swiftmailer.org', 'mark@swiftmailer.org')); + + $this->assertEqual( + array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null), + $header->getNameAddresses() + ); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testNameIsEncodedIfNonAscii() + { + $name = 'C' . pack('C', 0x8F) . 'rbyn'; + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($name, any(), any()) -> returns('C=8Frbyn') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('From', $encoder); + $header->setNameAddresses(array('chris@swiftmailer.org'=>'Chris ' . $name)); + + $addresses = $header->getNameAddressStrings(); + $this->assertEqual( + 'Chris =?' . $this->_charset . '?Q?C=8Frbyn?= ', + array_shift($addresses) + ); + } + + public function testEncodingLineLengthCalculations() + { + /* -- RFC 2047, 2. + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. + */ + + $name = 'C' . pack('C', 0x8F) . 'rbyn'; + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($name, 6, 63) -> returns('C=8Frbyn') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('From', $encoder); + $header->setNameAddresses(array('chris@swiftmailer.org'=>'Chris ' . $name)); + + $header->getNameAddressStrings(); + } + + public function testGetValueReturnsMailboxStringValue() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $this->assertEqual( + 'Chris Corbyn ', $header->getFieldBody() + ); + } + + public function testGetValueReturnsMailboxStringValueForMultipleMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + 'Chris Corbyn , Mark Corbyn ', + $header->getFieldBody() + ); + } + + public function testRemoveAddressesWithSingleValue() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $header->removeAddresses('chris@swiftmailer.org'); + $this->assertEqual(array('mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testRemoveAddressesWithList() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $header->removeAddresses( + array('chris@swiftmailer.org', 'mark@swiftmailer.org') + ); + $this->assertEqual(array(), $header->getAddresses()); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setFieldBodyModel('chris@swiftmailer.org'); + $this->assertEqual(array('chris@swiftmailer.org'=>null), $header->getNameAddresses()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array('chris@swiftmailer.org')); + $this->assertEqual(array('chris@swiftmailer.org'=>null), $header->getFieldBodyModel()); + } + + public function testToString() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + 'From: Chris Corbyn , ' . + 'Mark Corbyn ' . "\r\n", + $header->toString() + ); + } + + // -- Private methods + + private function _getHeader($name, $encoder) + { + $header = new Swift_Mime_Headers_MailboxHeader($name, $encoder); + $header->setCharset($this->_charset); + return $header; + } + + private function _getEncoder($type, $stub = false) + { + $encoder = $this->_mock('Swift_Mime_HeaderEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($type) + ); + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($encoder) + ); + } + return $encoder; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php b/lib/Swift/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php new file mode 100644 index 0000000..8b6f8f4 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php @@ -0,0 +1,425 @@ +_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $this->assertEqual(Swift_Mime_Header::TYPE_PARAMETERIZED, $header->getFieldType()); + } + + public function testValueIsReturnedVerbatim() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $this->assertEqual('text/plain', $header->getValue()); + } + + public function testParametersAreAppended() + { + /* -- RFC 2045, 5.1 + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + */ + + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $header->setParameters(array('charset' => 'utf-8')); + $this->assertEqual('text/plain; charset=utf-8', $header->getFieldBody()); + } + + public function testSpaceInParamResultsInQuotedString() + { + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => 'my file.txt')); + $this->assertEqual('attachment; filename="my file.txt"', + $header->getFieldBody() + ); + } + + public function testLongParamsAreBrokenIntoMultipleAttributeStrings() + { + /* -- RFC 2231, 3. + The asterisk character ("*") followed + by a decimal count is employed to indicate that multiple parameters + are being used to encapsulate a single parameter value. The count + starts at 0 and increments by 1 for each subsequent section of the + parameter value. Decimal values are used and neither leading zeroes + nor gaps in the sequence are allowed. + + The original parameter value is recovered by concatenating the + various sections of the parameter, in order. For example, the + content-type field + + Content-Type: message/external-body; access-type=URL; + URL*0="ftp://"; + URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + is semantically identical to + + Content-Type: message/external-body; access-type=URL; + URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + Note that quotes around parameter values are part of the value + syntax; they are NOT part of the value itself. Furthermore, it is + explicitly permitted to have a mixture of quoted and unquoted + continuation fields. + */ + + $value = str_repeat('a', 180); + + $encoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), 63) + -> returns(str_repeat('a', 63) . "\r\n" . + str_repeat('a', 63) . "\r\n" . str_repeat('a', 54)) + + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $this->assertEqual( + 'attachment; ' . + 'filename*0=' . str_repeat('a', 63) . ";\r\n " . + 'filename*1=' . str_repeat('a', 63) . ";\r\n " . + 'filename*2=' . str_repeat('a', 54), + $header->getFieldBody() + ); + } + + public function testEncodedParamDataIncludesCharsetAndLanguage() + { + /* -- RFC 2231, 4. + Asterisks ("*") are reused to provide the indicator that language and + character set information is present and encoding is being used. A + single quote ("'") is used to delimit the character set and language + information at the beginning of the parameter value. Percent signs + ("%") are used as the encoding flag, which agrees with RFC 2047. + + Specifically, an asterisk at the end of a parameter name acts as an + indicator that character set and language information may appear at + the beginning of the parameter value. A single quote is used to + separate the character set, language, and actual value information in + the parameter value string, and an percent sign is used to flag + octets encoded in hexadecimal. For example: + + Content-Type: application/x-stuff; + title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A + + Note that it is perfectly permissible to leave either the character + set or language field blank. Note also that the single quote + delimiters MUST be present even when one of the field values is + omitted. + */ + + $value = str_repeat('a', 20) . pack('C', 0x8F) . str_repeat('a', 10); + + $encoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, 12, 62) + -> returns(str_repeat('a', 20) . '%8F' . str_repeat('a', 10)) + + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $header->setLanguage($this->_lang); + $this->assertEqual( + 'attachment; filename*=' . $this->_charset . "'" . $this->_lang . "'" . + str_repeat('a', 20) . '%8F' . str_repeat('a', 10), + $header->getFieldBody() + ); + } + + public function testMultipleEncodedParamLinesAreFormattedCorrectly() + { + /* -- RFC 2231, 4.1. + Character set and language information may be combined with the + parameter continuation mechanism. For example: + + Content-Type: application/x-stuff + title*0*=us-ascii'en'This%20is%20even%20more%20 + title*1*=%2A%2A%2Afun%2A%2A%2A%20 + title*2="isn't it!" + + Note that: + + (1) Language and character set information only appear at + the beginning of a given parameter value. + + (2) Continuations do not provide a facility for using more + than one character set or language in the same + parameter value. + + (3) A value presented using multiple continuations may + contain a mixture of encoded and unencoded segments. + + (4) The first segment of a continuation MUST be encoded if + language and character set information are given. + + (5) If the first segment of a continued parameter value is + encoded the language and character set field delimiters + MUST be present even when the fields are left blank. + */ + + $value = str_repeat('a', 20) . pack('C', 0x8F) . str_repeat('a', 60); + + $encoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, 12, 62) + -> returns(str_repeat('a', 20) . '%8F' . str_repeat('a', 28) . "\r\n" . + str_repeat('a', 32)) + + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $header->setLanguage($this->_lang); + $this->assertEqual( + 'attachment; filename*0*=' . $this->_charset . "'" . $this->_lang . "'" . + str_repeat('a', 20) . '%8F' . str_repeat('a', 28) . ";\r\n " . + 'filename*1*=' . str_repeat('a', 32), + $header->getFieldBody() + ); + } + + public function testToString() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/html'); + $header->setParameters(array('charset' => 'utf-8')); + $this->assertEqual('Content-Type: text/html; charset=utf-8' . "\r\n", + $header->toString() + ); + } + + public function testValueCanBeEncodedIfNonAscii() + { + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, $this->_getParameterEncoder(true)); + $header->setValue($value); + $header->setParameters(array('lookslike' => 'foobar')); + $this->assertEqual('X-Foo: =?utf-8?Q?fo=8Fbar?=; lookslike=foobar' . "\r\n", + $header->toString() + ); + } + + public function testValueAndParamCanBeEncodedIfNonAscii() + { + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $paramEncoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($paramEncoder)->encodeString($value, any(), any()) -> returns('fo%8Fbar') + -> ignoring($paramEncoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder); + $header->setValue($value); + $header->setParameters(array('says' => $value)); + $this->assertEqual("X-Foo: =?utf-8?Q?fo=8Fbar?=; says*=utf-8''fo%8Fbar\r\n", + $header->toString() + ); + } + + public function testParamsAreEncodedWithEncodedWordsIfNoParamEncoderSet() + { + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, null); + $header->setValue('bar'); + $header->setParameters(array('says' => $value)); + $this->assertEqual("X-Foo: bar; says=\"=?utf-8?Q?fo=8Fbar?=\"\r\n", + $header->toString() + ); + } + + public function testLanguageInformationAppearsInEncodedWords() + { + /* -- RFC 2231, 5. + 5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + */ + + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $paramEncoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($paramEncoder)->encodeString($value, any(), any()) -> returns('fo%8Fbar') + -> ignoring($paramEncoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder); + $header->setLanguage('en'); + $header->setValue($value); + $header->setParameters(array('says' => $value)); + $this->assertEqual("X-Foo: =?utf-8*en?Q?fo=8Fbar?=; says*=utf-8'en'fo%8Fbar\r\n", + $header->toString() + ); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setFieldBodyModel('text/html'); + $this->assertEqual('text/html', $header->getValue()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $this->assertEqual('text/plain', $header->getFieldBodyModel()); + } + + public function testSetParameter() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setParameters(array('charset'=>'utf-8', 'delsp' => 'yes')); + $header->setParameter('delsp', 'no'); + $this->assertEqual(array('charset'=>'utf-8', 'delsp'=>'no'), + $header->getParameters() + ); + } + + public function testGetParameter() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setParameters(array('charset'=>'utf-8', 'delsp' => 'yes')); + $this->assertEqual('utf-8', $header->getParameter('charset')); + } + + // -- Private helper + + private function _getHeader($name, $encoder, $paramEncoder) + { + $header = new Swift_Mime_Headers_ParameterizedHeader($name, $encoder, + $paramEncoder + ); + $header->setCharset($this->_charset); + return $header; + } + + private function _getHeaderEncoder($type, $stub = false) + { + $encoder = $this->_mock('Swift_Mime_HeaderEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($type) + ); + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($encoder) + ); + } + return $encoder; + } + + private function _getParameterEncoder($stub = false) + { + if ($stub) + { + return $this->_stub('Swift_Encoder'); + } + else + { + return $this->_mock('Swift_Encoder'); + } + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/Headers/PathHeaderTest.php b/lib/Swift/tests/unit/Swift/Mime/Headers/PathHeaderTest.php new file mode 100644 index 0000000..7db1c88 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/Headers/PathHeaderTest.php @@ -0,0 +1,87 @@ +_getHeader('Return-Path'); + $this->assertEqual(Swift_Mime_Header::TYPE_PATH, $header->getFieldType()); + } + + public function testSingleAddressCanBeSetAndFetched() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEqual('chris@swiftmailer.org', $header->getAddress()); + } + + public function testAddressMustComplyWithRfc2822() + { + try + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chr is@swiftmailer.org'); + $this->fail('Address must be valid according to RFC 2822 addr-spec grammar.'); + } + catch (Exception $e) + { + $this->pass(); + } + } + + public function testValueIsAngleAddrWithValidAddress() + { + /* -- RFC 2822, 3.6.7. + return = "Return-Path:" path CRLF + + path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) / + obs-path + */ + + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testValueIsEmptyAngleBracketsIfEmptyAddressSet() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress(''); + $this->assertEqual('<>', $header->getFieldBody()); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Return-Path'); + $header->setFieldBodyModel('foo@bar.tld'); + $this->assertEqual('foo@bar.tld', $header->getAddress()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('foo@bar.tld'); + $this->assertEqual('foo@bar.tld', $header->getFieldBodyModel()); + } + + public function testToString() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEqual('Return-Path: ' . "\r\n", + $header->toString() + ); + } + + // -- Private methods + + private function _getHeader($name) + { + return new Swift_Mime_Headers_PathHeader($name); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php b/lib/Swift/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php new file mode 100644 index 0000000..71ba8d5 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php @@ -0,0 +1,368 @@ +_getHeader('Subject', $this->_getEncoder('Q', true)); + $this->assertEqual(Swift_Mime_Header::TYPE_TEXT, $header->getFieldType()); + } + + public function testGetNameReturnsNameVerbatim() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $this->assertEqual('Subject', $header->getFieldName()); + } + + public function testGetValueReturnsValueVerbatim() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('Test'); + $this->assertEqual('Test', $header->getValue()); + } + + public function testBasicStructureIsKeyValuePair() + { + /* -- RFC 2822, 2.2 + Header fields are lines composed of a field name, followed by a colon + (":"), followed by a field body, and terminated by CRLF. + */ + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('Test'); + $this->assertEqual('Subject: Test' . "\r\n", $header->toString()); + } + + public function testLongHeadersAreFoldedAtWordBoundary() + { + /* -- RFC 2822, 2.2.3 + Each header field is logically a single line of characters comprising + the field name, the colon, and the field body. For convenience + however, and to deal with the 998/78 character limitations per line, + the field body portion of a header field can be split into a multiple + line representation; this is called "folding". The general rule is + that wherever this standard allows for folding white space (not + simply WSP characters), a CRLF may be inserted before any WSP. + */ + + $value = 'The quick brown fox jumped over the fence, he was a very very ' . + 'scary brown fox with a bushy tail'; + $header = $this->_getHeader('X-Custom-Header', + $this->_getEncoder('Q', true) + ); + $header->setValue($value); + $header->setMaxLineLength(78); //A safe [RFC 2822, 2.2.3] default + /* + X-Custom-Header: The quick brown fox jumped over the fence, he was a very very + scary brown fox with a bushy tail + */ + $this->assertEqual( + 'X-Custom-Header: The quick brown fox jumped over the fence, he was a' . + ' very very' . "\r\n" . //Folding + ' scary brown fox with a bushy tail' . "\r\n", + $header->toString(), '%s: The header should have been folded at 78th char' + ); + } + + public function testPrintableAsciiOnlyAppearsInHeaders() + { + /* -- RFC 2822, 2.2. + A field name MUST be composed of printable US-ASCII characters (i.e., + characters that have values between 33 and 126, inclusive), except + colon. A field body may be composed of any US-ASCII characters, + except for CR and LF. + */ + + $nonAsciiChar = pack('C', 0x8F); + $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true)); + $header->setValue($nonAsciiChar); + $this->assertPattern( + '~^[^:\x00-\x20\x80-\xFF]+: [^\x80-\xFF\r\n]+\r\n$~s', + $header->toString() + ); + } + + public function testEncodedWordsFollowGeneralStructure() + { + /* -- RFC 2047, 1. + Generally, an "encoded-word" is a sequence of printable ASCII + characters that begins with "=?", ends with "?=", and has two "?"s in + between. + */ + + $nonAsciiChar = pack('C', 0x8F); + $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true)); + $header->setValue($nonAsciiChar); + $this->assertPattern( + '~^X-Test: \=?.*?\?.*?\?.*?\?=\r\n$~s', + $header->toString() + ); + } + + public function testEncodedWordIncludesCharsetAndEncodingMethodAndText() + { + /* -- RFC 2047, 2. + An 'encoded-word' is defined by the following ABNF grammar. The + notation of RFC 822 is used, with the exception that white space + characters MUST NOT appear between components of an 'encoded-word'. + + encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" + */ + + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($nonAsciiChar, any(), any()) -> returns('=8F') + -> ignoring($encoder) + ); + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + $this->assertEqual( + 'X-Test: =?' . $this->_charset . '?Q?=8F?=' . "\r\n", + $header->toString() + ); + } + + public function testEncodedWordsAreUsedToEncodedNonPrintableAscii() + { + //SPACE and TAB permitted + $nonPrintableBytes = array_merge( + range(0x00, 0x08), range(0x10, 0x19), array(0x7F) + ); + + foreach ($nonPrintableBytes as $byte) + { + $char = pack('C', $byte); + $encodedChar = sprintf('=%02X', $byte); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($char, any(), any()) -> returns($encodedChar) + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-A', $encoder); + $header->setValue($char); + + $this->assertEqual( + 'X-A: =?' . $this->_charset . '?Q?' . $encodedChar . '?=' . "\r\n", + $header->toString(), '%s: Non-printable ascii should be encoded' + ); + } + } + + public function testEncodedWordsAreUsedToEncode8BitOctets() + { + $_8BitBytes = range(0x80, 0xFF); + + foreach ($_8BitBytes as $byte) + { + $char = pack('C', $byte); + $encodedChar = sprintf('=%02X', $byte); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($char, any(), any()) -> returns($encodedChar) + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-A', $encoder); + $header->setValue($char); + + $this->assertEqual( + 'X-A: =?' . $this->_charset . '?Q?' . $encodedChar . '?=' . "\r\n", + $header->toString(), '%s: 8-bit octets should be encoded' + ); + } + } + + public function testEncodedWordsAreNoMoreThan75CharsPerLine() + { + /* -- RFC 2047, 2. + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. + + ... SNIP ... + + While there is no limit to the length of a multiple-line header + field, each line of a header field that contains one or more + 'encoded-word's is limited to 76 characters. + */ + + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($nonAsciiChar, 8, 63) -> returns('=8F') + -> ignoring($encoder) + ); + //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76 + //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63 + + //* X-Test: is 8 chars + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + + $this->assertEqual( + 'X-Test: =?' . $this->_charset . '?Q?=8F?=' . "\r\n", + $header->toString() + ); + } + + public function testFWSPIsUsedWhenEncoderReturnsMultipleLines() + { + /* --RFC 2047, 2. + If it is desirable to encode more text than will fit in an 'encoded-word' of + 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may + be used. + */ + + //Note the Mock does NOT return 8F encoded, the 8F merely triggers + // encoding for the sake of testing + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($nonAsciiChar, 8, 63) + -> returns('line_one_here' . "\r\n" . 'line_two_here') + -> ignoring($encoder) + ); + + //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76 + //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63 + + //* X-Test: is 8 chars + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + + $this->assertEqual( + 'X-Test: =?' . $this->_charset . '?Q?line_one_here?=' . "\r\n" . + ' =?' . $this->_charset . '?Q?line_two_here?=' . "\r\n", + $header->toString() + ); + } + + public function testAdjacentWordsAreEncodedTogether() + { + /* -- RFC 2047, 5 (1) + Ordinary ASCII text and 'encoded-word's may appear together in the + same header field. However, an 'encoded-word' that appears in a + header field defined as '*text' MUST be separated from any adjacent + 'encoded-word' or 'text' by 'linear-white-space'. + + -- RFC 2047, 2. + IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's + by an RFC 822 parser. As a consequence, unencoded white space + characters (such as SPACE and HTAB) are FORBIDDEN within an + 'encoded-word'. + */ + + //It would be valid to encode all words needed, however it's probably + // easiest to encode the longest amount required at a time + + $word = 'w' . pack('C', 0x8F) . 'rd'; + $text = 'start ' . $word . ' ' . $word . ' then end ' . $word; + // 'start', ' word word', ' and end', ' word' + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($word . ' ' . $word, any(), any()) + -> returns('w=8Frd_w=8Frd') + -> one($encoder)->encodeString($word, any(), any()) -> returns('w=8Frd') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($text); + + $headerString = $header->toString(); + + $this->assertEqual('X-Test: start =?' . $this->_charset . '?Q?' . + 'w=8Frd_w=8Frd?= then end =?' . $this->_charset . '?Q?'. + 'w=8Frd?=' . "\r\n", $headerString, + '%s: Adjacent encoded words should appear grouped with WSP encoded' + ); + } + + public function testLanguageInformationAppearsInEncodedWords() + { + /* -- RFC 2231, 5. + 5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + */ + + $value = 'fo' . pack('C', 0x8F) . 'bar'; + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Subject', $encoder); + $header->setLanguage('en'); + $header->setValue($value); + $this->assertEqual("Subject: =?utf-8*en?Q?fo=8Fbar?=\r\n", + $header->toString() + ); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setFieldBodyModel('test'); + $this->assertEqual('test', $header->getValue()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('test'); + $this->assertEqual('test', $header->getFieldBodyModel()); + } + + // -- Private methods + + private function _getHeader($name, $encoder) + { + $header = new Swift_Mime_Headers_UnstructuredHeader($name, $encoder); + $header->setCharset($this->_charset); + return $header; + } + + private function _getEncoder($type, $stub = false) + { + $encoder = $this->_mock('Swift_Mime_HeaderEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($type) + ); + if ($stub) + { + $this->_checking(Expectations::create() + -> ignoring($encoder) + ); + } + return $encoder; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/MimePartTest.php b/lib/Swift/tests/unit/Swift/Mime/MimePartTest.php new file mode 100644 index 0000000..ecaab3b --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/MimePartTest.php @@ -0,0 +1,253 @@ +_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, $part->getNestingLevel() + ); + } + + public function testCharsetIsReturnedFromHeader() + { + /* -- RFC 2046, 4.1.2. + A critical parameter that may be specified in the Content-Type field + for "text/plain" data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=iso-8859-1 + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('iso-8859-1', $part->getCharset()); + } + + public function testCharsetIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('charset', 'utf-8') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testCharsetIsSetInHeaderIfPassedToSetBody() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('charset', 'utf-8') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setBody('', 'text/plian', 'utf-8'); + } + + public function testSettingCharsetNotifiesEncoder() + { + $encoder = $this->_createEncoder('quoted-printable', false); + $this->_checking(Expectations::create() + -> one($encoder)->charsetChanged('utf-8') + -> ignoring($encoder) + ); + $part = $this->_createMimePart($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testSettingCharsetNotifiesHeaders() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->charsetChanged('utf-8') + -> ignoring($headers) + ); + $part = $this->_createMimePart($headers, $this->_createEncoder(), + $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testSettingCharsetNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + + $this->_checking(Expectations::create() + -> one($child)->charsetChanged('windows-874') + -> ignoring($child) + ); + + $part = $this->_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $part->setChildren(array($child)); + $part->setCharset('windows-874'); + } + + public function testCharsetChangeUpdatesCharset() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('charset', 'utf-8') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->charsetChanged('utf-8'); + } + + public function testSettingCharsetClearsCache() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setCharset('iso-2022'); + } + + public function testFormatIsReturnedFromHeader() + { + /* -- RFC 3676. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('format' => 'flowed') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('flowed', $part->getFormat()); + } + + public function testFormatIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('format', 'fixed') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setFormat('fixed'); + } + + public function testDelSpIsReturnedFromHeader() + { + /* -- RFC 3676. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('delsp' => 'no') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertIdentical(false, $part->getDelSp()); + } + + public function testDelSpIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('delsp', 'yes') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setDelSp(true); + } + + public function testFluidInterface() + { + $part = $this->_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertSame($part, + $part + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setCharset('utf-8') + ->setFormat('flowed') + ->setDelSp(true) + ); + } + + // -- Private helpers + + //abstract + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createMimePart($headers, $encoder, $cache); + } + + protected function _createMimePart($headers, $encoder, $cache) + { + return new Swift_Mime_MimePart($headers, $encoder, $cache); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php b/lib/Swift/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php new file mode 100644 index 0000000..9b5472f --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php @@ -0,0 +1,179 @@ +_factory = $this->_createFactory(); + } + + public function testMailboxHeaderIsCorrectType() + { + $header = $this->_factory->createMailboxHeader('X-Foo'); + $this->assertIsA($header, 'Swift_Mime_Headers_MailboxHeader'); + } + + public function testMailboxHeaderHasCorrectName() + { + $header = $this->_factory->createMailboxHeader('X-Foo'); + $this->assertEqual('X-Foo', $header->getFieldName()); + } + + public function testMailboxHeaderHasCorrectModel() + { + $header = $this->_factory->createMailboxHeader('X-Foo', + array('foo@bar'=>'FooBar') + ); + $this->assertEqual(array('foo@bar'=>'FooBar'), $header->getFieldBodyModel()); + } + + public function testDateHeaderHasCorrectType() + { + $header = $this->_factory->createDateHeader('X-Date'); + $this->assertIsA($header, 'Swift_Mime_Headers_DateHeader'); + } + + public function testDateHeaderHasCorrectName() + { + $header = $this->_factory->createDateHeader('X-Date'); + $this->assertEqual('X-Date', $header->getFieldName()); + } + + public function testDateHeaderHasCorrectModel() + { + $header = $this->_factory->createDateHeader('X-Date', 123); + $this->assertEqual(123, $header->getFieldBodyModel()); + } + + public function testTextHeaderHasCorrectType() + { + $header = $this->_factory->createTextHeader('X-Foo'); + $this->assertIsA($header, 'Swift_Mime_Headers_UnstructuredHeader'); + } + + public function testTextHeaderHasCorrectName() + { + $header = $this->_factory->createTextHeader('X-Foo'); + $this->assertEqual('X-Foo', $header->getFieldName()); + } + + public function testTextHeaderHasCorrectModel() + { + $header = $this->_factory->createTextHeader('X-Foo', 'bar'); + $this->assertEqual('bar', $header->getFieldBodyModel()); + } + + public function testParameterizedHeaderHasCorrectType() + { + $header = $this->_factory->createParameterizedHeader('X-Foo'); + $this->assertIsA($header, 'Swift_Mime_Headers_ParameterizedHeader'); + } + + public function testParameterizedHeaderHasCorrectName() + { + $header = $this->_factory->createParameterizedHeader('X-Foo'); + $this->assertEqual('X-Foo', $header->getFieldName()); + } + + public function testParameterizedHeaderHasCorrectModel() + { + $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar'); + $this->assertEqual('bar', $header->getFieldBodyModel()); + } + + public function testParameterizedHeaderHasCorrectParams() + { + $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar', + array('zip' => 'button') + ); + $this->assertEqual(array('zip'=>'button'), $header->getParameters()); + } + + public function testIdHeaderHasCorrectType() + { + $header = $this->_factory->createIdHeader('X-ID'); + $this->assertIsA($header, 'Swift_Mime_Headers_IdentificationHeader'); + } + + public function testIdHeaderHasCorrectName() + { + $header = $this->_factory->createIdHeader('X-ID'); + $this->assertEqual('X-ID', $header->getFieldName()); + } + + public function testIdHeaderHasCorrectModel() + { + $header = $this->_factory->createIdHeader('X-ID', 'xyz@abc'); + $this->assertEqual(array('xyz@abc'), $header->getFieldBodyModel()); + } + + public function testPathHeaderHasCorrectType() + { + $header = $this->_factory->createPathHeader('X-Path'); + $this->assertIsA($header, 'Swift_Mime_Headers_PathHeader'); + } + + public function testPathHeaderHasCorrectName() + { + $header = $this->_factory->createPathHeader('X-Path'); + $this->assertEqual('X-Path', $header->getFieldName()); + } + + public function testPathHeaderHasCorrectModel() + { + $header = $this->_factory->createPathHeader('X-Path', 'foo@bar'); + $this->assertEqual('foo@bar', $header->getFieldBodyModel()); + } + + public function testCharsetChangeNotificationNotifiesEncoders() + { + $encoder = $this->_createHeaderEncoder(false); + $paramEncoder = $this->_createParamEncoder(false); + + $factory = $this->_createFactory($encoder, $paramEncoder); + + $this->_checking(Expectations::create() + -> one($encoder)->charsetChanged('utf-8') + -> one($paramEncoder)->charsetChanged('utf-8') + -> ignoring($encoder) + -> ignoring($paramEncoder) + ); + + $factory->charsetChanged('utf-8'); + } + + // -- Creation methods + + private function _createFactory($encoder = null, $paramEncoder = null) + { + return new Swift_Mime_SimpleHeaderFactory( + $encoder + ? $encoder : $this->_createHeaderEncoder(), + $paramEncoder + ? $paramEncoder : $this->_createParamEncoder() + ); + } + + private function _createHeaderEncoder($stub = true) + { + return $stub + ? $this->_stub('Swift_Mime_HeaderEncoder') + : $this->_mock('Swift_Mime_HeaderEncoder'); + } + + private function _createParamEncoder($stub = true) + { + return $stub + ? $this->_stub('Swift_Encoder') + : $this->_mock('Swift_Encoder'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/SimpleHeaderSetTest.php b/lib/Swift/tests/unit/Swift/Mime/SimpleHeaderSetTest.php new file mode 100644 index 0000000..ccc017d --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/SimpleHeaderSetTest.php @@ -0,0 +1,654 @@ +_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createMailboxHeader('From', array('person@domain'=>'Person')) + -> returns($this->_createHeader('From')) + ); + $set = $this->_createSet($factory); + $set->addMailboxHeader('From', array('person@domain'=>'Person')); + } + + public function testAddDateHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createDateHeader('Date', 1234) + -> returns($this->_createHeader('Date')) + ); + $set = $this->_createSet($factory); + $set->addDateHeader('Date', 1234); + } + + public function testAddTextHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Subject', 'some text') + -> returns($this->_createHeader('Subject')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + } + + public function testAddParameterizedHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createParameterizedHeader( + 'Content-Type', 'text/plain', array('charset'=>'utf-8') + ) -> returns($this->_createHeader('Content-Type')) + ); + $set = $this->_createSet($factory); + $set->addParameterizedHeader('Content-Type', 'text/plain', + array('charset'=>'utf-8') + ); + } + + public function testAddIdHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + } + + public function testAddPathHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createPathHeader('Return-Path', 'some@path') + -> returns($this->_createHeader('Return-Path')) + ); + $set = $this->_createSet($factory); + $set->addPathHeader('Return-Path', 'some@path'); + } + + public function testHasReturnsFalseWhenNoHeaders() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertFalse($set->has('Some-Header')); + } + + public function testAddedMailboxHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createMailboxHeader('From', array('person@domain'=>'Person')) + -> returns($this->_createHeader('From')) + ); + $set = $this->_createSet($factory); + $set->addMailboxHeader('From', array('person@domain'=>'Person')); + $this->assertTrue($set->has('From')); + } + + public function testAddedDateHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createDateHeader('Date', 1234) + -> returns($this->_createHeader('Date')) + ); + $set = $this->_createSet($factory); + $set->addDateHeader('Date', 1234); + $this->assertTrue($set->has('Date')); + } + + public function testAddedTextHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createTextHeader('Subject', 'some text') + -> returns($this->_createHeader('Subject')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $this->assertTrue($set->has('Subject')); + } + + public function testAddedParameterizedHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createParameterizedHeader( + 'Content-Type', 'text/plain', array('charset'=>'utf-8') + ) -> returns($this->_createHeader('Content-Type')) + ); + $set = $this->_createSet($factory); + $set->addParameterizedHeader('Content-Type', 'text/plain', + array('charset'=>'utf-8') + ); + $this->assertTrue($set->has('Content-Type')); + } + + public function testAddedIdHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('Message-ID')); + } + + public function testAddedPathHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createPathHeader('Return-Path', 'some@path') + -> returns($this->_createHeader('Return-Path')) + ); + $set = $this->_createSet($factory); + $set->addPathHeader('Return-Path', 'some@path'); + $this->assertTrue($set->has('Return-Path')); + } + + public function testNewlySetHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $header = $this->_createHeader('X-Foo', 'bar'); + $set = $this->_createSet($factory); + $set->set($header); + $this->assertTrue($set->has('X-Foo')); + } + + public function testHasCanAcceptOffset() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('Message-ID', 0)); + } + + public function testHasWithIllegalOffsetReturnsFalse() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testHasCanDistinguishMultipleHeaders() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $this->assertTrue($set->has('Message-ID', 1)); + } + + public function testGetWithUnspecifiedOffset() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertSame($header, $set->get('Message-ID')); + } + + public function testGetWithSpeiciedOffset() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $header2 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + -> ignoring($factory)->createIdHeader('Message-ID', 'more@id') + -> returns($header2) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->addIdHeader('Message-ID', 'more@id'); + $this->assertSame($header1, $set->get('Message-ID', 1)); + } + + public function testGetReturnsNullIfHeaderNotSet() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertNull($set->get('Message-ID', 99)); + } + + public function testGetAllReturnsAllHeadersMatchingName() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $header2 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + -> ignoring($factory)->createIdHeader('Message-ID', 'more@id') + -> returns($header2) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->addIdHeader('Message-ID', 'more@id'); + + $this->assertEqual(array($header0, $header1, $header2), + $set->getAll('Message-ID') + ); + } + + public function testGetAllReturnsAllHeadersIfNoArguments() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Subject'); + $header2 = $this->_createHeader('To'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Subject', 'thing') + -> returns($header1) + -> ignoring($factory)->createIdHeader('To', 'person@example.org') + -> returns($header2) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Subject', 'thing'); + $set->addIdHeader('To', 'person@example.org'); + + $this->assertEqual(array($header0, $header1, $header2), + $set->getAll() + ); + } + + public function testGetAllReturnsEmptyArrayIfNoneSet() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertEqual(array(), $set->getAll('Received')); + } + + public function testRemoveWithUnspecifiedOffset() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('Message-ID'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testRemoveWithSpecifiedIndexRemovesHeader() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->remove('Message-ID', 1); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testRemoveWithSpecifiedIndexLeavesOtherHeaders() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->remove('Message-ID', 1); + $this->assertTrue($set->has('Message-ID', 0)); + } + + public function testRemoveWithInvalidOffsetDoesNothing() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('Message-ID', 50); + $this->assertTrue($set->has('Message-ID')); + } + + public function testRemoveAllRemovesAllHeadersWithName() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->removeAll('Message-ID'); + $this->assertFalse($set->has('Message-ID', 0)); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testHasIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('message-id')); + } + + public function testGetIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertSame($header, $set->get('message-id')); + } + + public function testGetAllIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertEqual(array($header), $set->getAll('message-id')); + } + + public function testRemoveIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('message-id'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testRemoveAllIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->removeAll('message-id'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testNewInstance() + { + $set = $this->_createSet($this->_createFactory()); + $instance = $set->newInstance(); + $this->assertIsA($instance, 'Swift_Mime_HeaderSet'); + } + + public function testToStringJoinsHeadersTogether() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Foo', 'bar') + -> returns($this->_createHeader('Foo', 'bar')) + -> one($factory)->createTextHeader('Zip', 'buttons') + -> returns($this->_createHeader('Zip', 'buttons')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', 'bar'); + $set->addTextHeader('Zip', 'buttons'); + $this->assertEqual( + "Foo: bar\r\n" . + "Zip: buttons\r\n", + $set->toString() + ); + } + + public function testHeadersWithoutBodiesAreNotDisplayed() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Foo', 'bar') + -> returns($this->_createHeader('Foo', 'bar')) + -> one($factory)->createTextHeader('Zip', '') + -> returns($this->_createHeader('Zip', '')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', 'bar'); + $set->addTextHeader('Zip', ''); + $this->assertEqual( + "Foo: bar\r\n", + $set->toString() + ); + } + + public function testHeadersWithoutBodiesCanBeForcedToDisplay() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Foo', '') + -> returns($this->_createHeader('Foo', '')) + -> one($factory)->createTextHeader('Zip', '') + -> returns($this->_createHeader('Zip', '')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', ''); + $set->addTextHeader('Zip', ''); + $set->setAlwaysDisplayed(array('Foo', 'Zip')); + $this->assertEqual( + "Foo: \r\n" . + "Zip: \r\n", + $set->toString() + ); + } + + public function testHeaderSequencesCanBeSpecified() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('First', 'one') + -> returns($this->_createHeader('First', 'one')) + -> one($factory)->createTextHeader('Second', 'two') + -> returns($this->_createHeader('Second', 'two')) + -> one($factory)->createTextHeader('Third', 'three') + -> returns($this->_createHeader('Third', 'three')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Third', 'three'); + $set->addTextHeader('First', 'one'); + $set->addTextHeader('Second', 'two'); + + $set->defineOrdering(array('First', 'Second', 'Third')); + + $this->assertEqual( + "First: one\r\n" . + "Second: two\r\n" . + "Third: three\r\n", + $set->toString() + ); + } + + public function testUnsortedHeadersAppearAtEnd() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('First', 'one') + -> returns($this->_createHeader('First', 'one')) + -> one($factory)->createTextHeader('Second', 'two') + -> returns($this->_createHeader('Second', 'two')) + -> one($factory)->createTextHeader('Third', 'three') + -> returns($this->_createHeader('Third', 'three')) + -> one($factory)->createTextHeader('Fourth', 'four') + -> returns($this->_createHeader('Fourth', 'four')) + -> one($factory)->createTextHeader('Fifth', 'five') + -> returns($this->_createHeader('Fifth', 'five')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Fourth', 'four'); + $set->addTextHeader('Fifth', 'five'); + $set->addTextHeader('Third', 'three'); + $set->addTextHeader('First', 'one'); + $set->addTextHeader('Second', 'two'); + + $set->defineOrdering(array('First', 'Second', 'Third')); + + $this->assertEqual( + "First: one\r\n" . + "Second: two\r\n" . + "Third: three\r\n" . + "Fourth: four\r\n" . + "Fifth: five\r\n", + $set->toString() + ); + } + + public function testSettingCharsetNotifiesAlreadyExistingHeaders() + { + $subject = $this->_createHeader('Subject', 'some text'); + $xHeader = $this->_createHeader('X-Header', 'some text'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createTextHeader('Subject', 'some text') + -> returns($subject) + -> ignoring($factory)->createTextHeader('X-Header', 'some text') + -> returns($xHeader) + -> ignoring($factory) + -> one($subject)->setCharset('utf-8') + -> one($xHeader)->setCharset('utf-8') + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $set->addTextHeader('X-Header', 'some text'); + + $set->setCharset('utf-8'); + } + + public function testCharsetChangeNotifiesAlreadyExistingHeaders() + { + $subject = $this->_createHeader('Subject', 'some text'); + $xHeader = $this->_createHeader('X-Header', 'some text'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createTextHeader('Subject', 'some text') + -> returns($subject) + -> ignoring($factory)->createTextHeader('X-Header', 'some text') + -> returns($xHeader) + -> ignoring($factory) + -> one($subject)->setCharset('utf-8') + -> one($xHeader)->setCharset('utf-8') + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $set->addTextHeader('X-Header', 'some text'); + + $set->charsetChanged('utf-8'); + } + + public function testCharsetChangeNotifiesFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->charsetChanged('utf-8') + -> ignoring($factory) + ); + $set = $this->_createSet($factory); + + $set->setCharset('utf-8'); + } + + // -- Creation methods + + private function _createSet($factory) + { + return new Swift_Mime_SimpleHeaderSet($factory); + } + + private function _createFactory() + { + return $this->_mock('Swift_Mime_HeaderFactory'); + } + + private function _createHeader($name, $body = '') + { + $header = $this->_mock('Swift_Mime_Header'); + $this->_checking(Expectations::create() + -> ignoring($header)->getFieldName() -> returns($name) + -> ignoring($header)->toString() -> returns(sprintf("%s: %s\r\n", $name, $body)) + -> ignoring($header)->getFieldBody() -> returns($body) + ); + return $header; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/SimpleMessageTest.php b/lib/Swift/tests/unit/Swift/Mime/SimpleMessageTest.php new file mode 100644 index 0000000..34ea9ff --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/SimpleMessageTest.php @@ -0,0 +1,804 @@ +_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_TOP, $message->getNestingLevel() + ); + } + + public function testDateIsReturnedFromHeader() + { + $date = $this->_createHeader('Date', 123); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Date' => $date)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(123, $message->getDate()); + } + + public function testDateIsSetInHeader() + { + $date = $this->_createHeader('Date', 123, array(), false); + $this->_checking(Expectations::create() + -> one($date)->setFieldBodyModel(1234) + -> ignoring($date) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Date' => $date)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setDate(1234); + } + + public function testDateHeaderIsCreatedIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addDateHeader('Date', 1234) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setDate(1234); + } + + public function testDateHeaderIsAddedDuringConstruction() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addDateHeader('Date', pattern('/^[0-9]+$/D')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testIdIsReturnedFromHeader() + { + /* -- RFC 2045, 7. + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field + */ + + $messageId = $this->_createHeader('Message-ID', 'a@b'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Message-ID' => $messageId)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('a@b', $message->getId()); + } + + public function testIdIsSetInHeader() + { + $messageId = $this->_createHeader('Message-ID', 'a@b', array(), false); + $this->_checking(Expectations::create() + -> one($messageId)->setFieldBodyModel('x@y') + -> ignoring($messageId) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Message-ID' => $messageId)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setId('x@y'); + } + + public function testIdIsAutoGenerated() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addIdHeader('Message-ID', pattern('/^.*?@.*?$/D')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testSubjectIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.5. + */ + + $subject = $this->_createHeader('Subject', 'example subject'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Subject' => $subject)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('example subject', $message->getSubject()); + } + + public function testSubjectIsSetInHeader() + { + $subject = $this->_createHeader('Subject', '', array(), false); + $this->_checking(Expectations::create() + -> one($subject)->setFieldBodyModel('foo') + -> ignoring($subject) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Subject' => $subject)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setSubject('foo'); + } + + public function testSubjectHeaderIsCreatedIfNotPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('Subject', 'example subject') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSubject('example subject'); + } + + public function testReturnPathIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.7. + */ + + $path = $this->_createHeader('Return-Path', 'bounces@domain'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Return-Path' => $path)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('bounces@domain', $message->getReturnPath()); + } + + public function testReturnPathIsSetInHeader() + { + $path = $this->_createHeader('Return-Path', '', array(), false); + $this->_checking(Expectations::create() + -> one($path)->setFieldBodyModel('bounces@domain') + -> ignoring($path) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Return-Path' => $path)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReturnPath('bounces@domain'); + } + + public function testReturnPathHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addPathHeader('Return-Path', 'bounces@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReturnPath('bounces@domain'); + } + + public function testSenderIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $sender = $this->_createHeader('Sender', array('sender@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Sender' => $sender)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('sender@domain'=>'Name'), $message->getSender()); + } + + public function testSenderIsSetInHeader() + { + $sender = $this->_createHeader('Sender', array('sender@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($sender)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($sender) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Sender' => $sender)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setSender(array('other@domain'=>'Other')); + } + + public function testSenderHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Sender', (array) 'sender@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSender('sender@domain'); + } + + public function testNameCanBeUsedInSenderHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Sender', array('sender@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSender('sender@domain', 'Name'); + } + + public function testFromIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $from = $this->_createHeader('From', array('from@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('from@domain'=>'Name'), $message->getFrom()); + } + + public function testFromIsSetInHeader() + { + $from = $this->_createHeader('From', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($from)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($from) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setFrom(array('other@domain'=>'Other')); + } + + public function testFromIsAddedToHeadersDuringAddFrom() + { + $from = $this->_createHeader('From', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($from)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($from) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addFrom('other@domain', 'Other'); + } + + public function testFromHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('From', (array) 'from@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setFrom('from@domain'); + } + + public function testPersonalNameCanBeUsedInFromAddress() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('From', array('from@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setFrom('from@domain', 'Name'); + } + + public function testReplyToIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $reply = $this->_createHeader('Reply-To', array('reply@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $reply)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('reply@domain'=>'Name'), $message->getReplyTo()); + } + + public function testReplyToIsSetInHeader() + { + $reply = $this->_createHeader('Reply-To', array('reply@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($reply)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($reply) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $reply)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReplyTo(array('other@domain'=>'Other')); + } + + public function testReplyToIsAddedToHeadersDuringAddReplyTo() + { + $replyTo = $this->_createHeader('Reply-To', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($replyTo)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($replyTo) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $replyTo)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addReplyTo('other@domain', 'Other'); + } + + public function testReplyToHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Reply-To', (array) 'reply@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReplyTo('reply@domain'); + } + + public function testNameCanBeUsedInReplyTo() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Reply-To', array('reply@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReplyTo('reply@domain', 'Name'); + } + + public function testToIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $to = $this->_createHeader('To', array('to@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('to@domain'=>'Name'), $message->getTo()); + } + + public function testToIsSetInHeader() + { + $to = $this->_createHeader('To', array('to@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($to)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($to) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setTo(array('other@domain'=>'Other')); + } + + public function testToIsAddedToHeadersDuringAddTo() + { + $to = $this->_createHeader('To', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($to)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($to) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addTo('other@domain', 'Other'); + } + + public function testToHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('To', (array) 'to@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setTo('to@domain'); + } + + public function testNameCanBeUsedInToHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('To', array('to@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setTo('to@domain', 'Name'); + } + + + public function testCcIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $cc = $this->_createHeader('Cc', array('cc@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('cc@domain'=>'Name'), $message->getCc()); + } + + public function testCcIsSetInHeader() + { + $cc = $this->_createHeader('Cc', array('cc@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($cc)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($cc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setCc(array('other@domain'=>'Other')); + } + + public function testCcIsAddedToHeadersDuringAddCc() + { + $cc = $this->_createHeader('Cc', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($cc)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($cc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addCc('other@domain', 'Other'); + } + + public function testCcHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Cc', (array) 'cc@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setCc('cc@domain'); + } + + public function testNameCanBeUsedInCcHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Cc', array('cc@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setCc('cc@domain', 'Name'); + } + + + public function testBccIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $bcc = $this->_createHeader('Bcc', array('bcc@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('bcc@domain'=>'Name'), $message->getBcc()); + } + + public function testBccIsSetInHeader() + { + $bcc = $this->_createHeader('Bcc', array('bcc@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($bcc)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($bcc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setBcc(array('other@domain'=>'Other')); + } + + public function testBccIsAddedToHeadersDuringAddBcc() + { + $bcc = $this->_createHeader('Bcc', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($bcc)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($bcc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addBcc('other@domain', 'Other'); + } + + public function testBccHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Bcc', (array) 'bcc@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setBcc('bcc@domain'); + } + + public function testNameCanBeUsedInBcc() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Bcc', array('bcc@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setBcc('bcc@domain', 'Name'); + } + + public function testPriorityIsReadFromHeader() + { + $prio = $this->_createHeader('X-Priority', '2 (High)'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('X-Priority' => $prio)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(2, $message->getPriority()); + } + + public function testPriorityIsSetInHeader() + { + $prio = $this->_createHeader('X-Priority', '2 (High)', array(), false); + $this->_checking(Expectations::create() + -> one($prio)->setFieldBodyModel('5 (Lowest)') + -> ignoring($prio) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('X-Priority' => $prio)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setPriority(5); + } + + public function testPriorityHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('X-Priority', '4 (Low)') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setPriority(4); + } + + public function testReadReceiptAddressReadFromHeader() + { + $rcpt = $this->_createHeader('Disposition-Notification-To', + array('chris@swiftmailer.org'=>'Chris') + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('chris@swiftmailer.org'=>'Chris'), + $message->getReadReceiptTo() + ); + } + + public function testReadReceiptIsSetInHeader() + { + $rcpt = $this->_createHeader('Disposition-Notification-To', array(), array(), false); + $this->_checking(Expectations::create() + -> one($rcpt)->setFieldBodyModel('mark@swiftmailer.org') + -> ignoring($rcpt) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReadReceiptTo('mark@swiftmailer.org'); + } + + public function testReadReceiptHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader( + 'Disposition-Notification-To', 'mark@swiftmailer.org' + ) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReadReceiptTo('mark@swiftmailer.org'); + } + + public function testChildrenCanBeAttached() + { + $child1 = $this->_createChild(); + $child2 = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->attach($child1); + $message->attach($child2); + + $this->assertEqual(array($child1, $child2), $message->getChildren()); + } + + public function testChildrenCanBeDetached() + { + $child1 = $this->_createChild(); + $child2 = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->attach($child1); + $message->attach($child2); + + $message->detach($child1); + + $this->assertEqual(array($child2), $message->getChildren()); + } + + public function testEmbedAttachesChild() + { + $child = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->embed($child); + + $this->assertEqual(array($child), $message->getChildren()); + } + + public function testEmbedReturnsValidCid() + { + $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_RELATED, '', + false + ); + $this->_checking(Expectations::create() + -> ignoring($child)->getId() -> returns('foo@bar') + -> ignoring($child) + ); + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertEqual('cid:foo@bar', $message->embed($child)); + } + + public function testFluidInterface() + { + $child = $this->_createChild(); + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertSame($message, + $message + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setCharset('iso-8859-1') + ->setFormat('flowed') + ->setDelSp(false) + ->setSubject('subj') + ->setDate(123) + ->setReturnPath('foo@bar') + ->setSender('foo@bar') + ->setFrom(array('x@y' => 'XY')) + ->setReplyTo(array('ab@cd' => 'ABCD')) + ->setTo(array('chris@site.tld', 'mark@site.tld')) + ->setCc('john@somewhere.tld') + ->setBcc(array('one@site', 'two@site' => 'Two')) + ->setPriority(4) + ->setReadReceiptTo('a@b') + ->attach($child) + ->detach($child) + ); + } + + // -- Private helpers + + //abstract + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createMessage($headers, $encoder, $cache); + } + + protected function _createMimePart($headers, $encoder, $cache) + { + return $this->_createMessage($headers, $encoder, $cache); + } + + private function _createMessage($headers, $encoder, $cache) + { + return new Swift_Mime_SimpleMessage($headers, $encoder, $cache); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Mime/SimpleMimeEntityTest.php b/lib/Swift/tests/unit/Swift/Mime/SimpleMimeEntityTest.php new file mode 100644 index 0000000..b1b2220 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Mime/SimpleMimeEntityTest.php @@ -0,0 +1,16 @@ +assertEqual(10, $plugin->getThreshold()); + $plugin->setThreshold(100); + $this->assertEqual(100, $plugin->getThreshold()); + } + + public function testSleepTimeCanBeSetAndFetched() + { + $plugin = new Swift_Plugins_AntiFloodPlugin(10, 5); + $this->assertEqual(5, $plugin->getSleepTime()); + $plugin->setSleepTime(1); + $this->assertEqual(1, $plugin->getSleepTime()); + } + + public function testPluginStopsConnectionAfterThreshold() + { + $transport = $this->_createTransport(); + $evt = $this->_createSendEvent($transport); + $this->_checking(Expectations::create() + -> one($transport)->start() + -> one($transport)->stop() + -> ignoring($transport) + ); + + $plugin = new Swift_Plugins_AntiFloodPlugin(10); + for ($i = 0; $i < 12; $i++) + { + $plugin->sendPerformed($evt); + } + } + + public function testPluginCanStopAndStartMultipleTimes() + { + $transport = $this->_createTransport(); + $evt = $this->_createSendEvent($transport); + $this->_checking(Expectations::create() + -> exactly(5)->of($transport)->start() + -> exactly(5)->of($transport)->stop() + -> ignoring($transport) + ); + + $plugin = new Swift_Plugins_AntiFloodPlugin(2); + for ($i = 0; $i < 11; $i++) + { + $plugin->sendPerformed($evt); + } + } + + public function testPluginCanSleepDuringRestart() + { + $sleeper = $this->_createSleeper(); + $transport = $this->_createTransport(); + $evt = $this->_createSendEvent($transport); + $this->_checking(Expectations::create() + -> one($sleeper)->sleep(10) + -> one($transport)->start() + -> one($transport)->stop() + -> ignoring($transport) + ); + + $plugin = new Swift_Plugins_AntiFloodPlugin(99, 10, $sleeper); + for ($i = 0; $i < 101; $i++) + { + $plugin->sendPerformed($evt); + } + } + + // -- Creation Methods + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createSendEvent($transport) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getSource() -> returns($transport) + -> ignoring($evt)->getTransport() -> returns($transport) + ); + return $evt; + } + + private function _createSleeper() + { + return $this->_mock('Swift_Plugins_Sleeper'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php b/lib/Swift/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php new file mode 100644 index 0000000..3cb0e4a --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php @@ -0,0 +1,127 @@ +_monitor = new Swift_Plugins_BandwidthMonitorPlugin(); + } + + public function testBytesOutIncreasesAccordingToMessageLength() + { + $message = $this->_createMessageWithByteCount(6); + $evt = $this->_createSendEvent($message); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->_monitor->sendPerformed($evt); + $this->assertEqual(6, $this->_monitor->getBytesOut()); + $this->_monitor->sendPerformed($evt); + $this->assertEqual(12, $this->_monitor->getBytesOut()); + } + + public function testBytesOutIncreasesWhenCommandsSent() + { + $evt = $this->_createCommandEvent("RCPT TO: \r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(24, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(48, $this->_monitor->getBytesOut()); + } + + public function testBytesInIncreasesWhenResponsesReceived() + { + $evt = $this->_createResponseEvent("250 Ok\r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(8, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(16, $this->_monitor->getBytesIn()); + } + + public function testCountersCanBeReset() + { + $evt = $this->_createResponseEvent("250 Ok\r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(8, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(16, $this->_monitor->getBytesIn()); + + $evt = $this->_createCommandEvent("RCPT TO: \r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(24, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(48, $this->_monitor->getBytesOut()); + + $this->_monitor->reset(); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->assertEqual(0, $this->_monitor->getBytesIn()); + } + + // -- Creation Methods + + private function _createSendEvent($message) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getMessage() -> returns($message) + ); + return $evt; + } + + private function _createCommandEvent($command) + { + $evt = $this->_mock('Swift_Events_CommandEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getCommand() -> returns($command) + ); + return $evt; + } + + private function _createResponseEvent($response) + { + $evt = $this->_mock('Swift_Events_ResponseEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getResponse() -> returns($response) + ); + return $evt; + } + + private function _createMessageWithByteCount($bytes) + { + $this->_bytes = $bytes; + $msg = $this->_mock('Swift_Mime_Message'); + $this->_checking(Expectations::create() + -> ignoring($msg)->toByteStream(any()) -> calls(array($this, '_write')) + ); + return $msg; + } + + private $_bytes = 0; + public function _write($invocation) + { + $args = $invocation->getArguments(); + $is = $args[0]; + for ($i = 0; $i < $this->_bytes; ++$i) + { + $is->write('x'); + } + } + +} \ No newline at end of file diff --git a/lib/Swift/tests/unit/Swift/Plugins/DecoratorPluginTest.php b/lib/Swift/tests/unit/Swift/Plugins/DecoratorPluginTest.php new file mode 100644 index 0000000..1c6b62c --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/DecoratorPluginTest.php @@ -0,0 +1,194 @@ +_createMessage( + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Hello {name}, you are customer #{id}' + ); + $this->_checking(Expectations::create() + -> one($message)->setBody('Hello Zip, you are customer #456') + -> ignoring($message) + ); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeAppliedToSameMessageMultipleTimes() + { + $message = $this->_createMessage( + array('zip@button.tld' => 'Zipathon', 'foo@bar.tld' => 'Foo'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Hello {name}, you are customer #{id}' + ); + $this->_checking(Expectations::create() + -> one($message)->setBody('Hello Zip, you are customer #456') + -> one($message)->setBody('Hello {name}, you are customer #{id}') + -> one($message)->setBody('Hello Foo, you are customer #123') + -> ignoring($message) + ); + + $plugin = $this->_createPlugin( + array( + 'foo@bar.tld' => array('{name}' => 'Foo', '{id}' => '123'), + 'zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456') + ) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeMadeInSubject() + { + $message = $this->_createMessage( + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'A message for {name}!', + 'Hello {name}, you are customer #{id}' + ); + $this->_checking(Expectations::create() + -> one($message)->setBody('Hello Zip, you are customer #456') + -> one($message)->setSubject('A message for Zip!') + -> ignoring($message) + ); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsAreMadeOnSubparts() + { + $part1 = $this->_createPart('text/plain', 'Your name is {name}?', '1@x'); + $part2 = $this->_createPart('text/html', 'Your name is {name}?', '2@x'); + $message = $this->_createMessage( + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'A message for {name}!', + 'Subject' + ); + $this->_checking(Expectations::create() + -> ignoring($message)->getChildren() -> returns(array($part1, $part2)) + -> one($part1)->setBody('Your name is Zip?') + -> one($part2)->setBody('Your name is Zip?') + -> ignoring($part1) + -> ignoring($part2) + -> ignoring($message) + ); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeTakenFromCustomReplacementsObject() + { + $message = $this->_createMessage( + array('foo@bar' => 'Foobar', 'zip@zap' => 'Zip zap'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Something {a}' + ); + + $replacements = $this->_createReplacements(); + + $this->_checking(Expectations::create() + -> one($message)->setBody('Something b') + -> one($message)->setBody('Something c') + -> one($replacements)->getReplacementsFor('foo@bar') -> returns(array('{a}'=>'b')) + -> one($replacements)->getReplacementsFor('zip@zap') -> returns(array('{a}'=>'c')) + -> ignoring($message) + ); + + $plugin = $this->_createPlugin($replacements); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + // -- Creation methods + + private function _createMessage($to = array(), $from = null, $subject = null, + $body = null) + { + $message = $this->_mock('Swift_Mime_Message'); + foreach ($to as $addr => $name) + { + $this->_checking(Expectations::create() + -> one($message)->getTo() -> returns(array($addr => $name)) + ); + } + $this->_checking(Expectations::create() + -> ignoring($message)->getFrom() -> returns($from) + -> ignoring($message)->getSubject() -> returns($subject) + -> ignoring($message)->getBody() -> returns($body) + ); + return $message; + } + + private function _createPlugin($replacements) + { + return new Swift_Plugins_DecoratorPlugin($replacements); + } + + private function _createReplacements() + { + return $this->_mock('Swift_Plugins_Decorator_Replacements'); + } + + private function _createSendEvent(Swift_Mime_Message $message) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getMessage() -> returns($message) + -> ignoring($evt) + ); + return $evt; + } + + private function _createPart($type, $body, $id) + { + $part = $this->_mock('Swift_Mime_MimeEntity'); + $this->_checking(Expectations::create() + -> ignoring($part)->getContentType() -> returns($type) + -> ignoring($part)->getBody() -> returns($body) + -> ignoring($part)->getId() -> returns($id) + ); + return $part; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Plugins/LoggerPluginTest.php b/lib/Swift/tests/unit/Swift/Plugins/LoggerPluginTest.php new file mode 100644 index 0000000..5062c16 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/LoggerPluginTest.php @@ -0,0 +1,199 @@ +_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add('foo') + ); + + $plugin = $this->_createPlugin($logger); + $plugin->add('foo'); + } + + public function testLoggerDelegatesDumpingEntries() + { + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->dump() -> returns('foobar') + ); + + $plugin = $this->_createPlugin($logger); + $this->assertEqual('foobar', $plugin->dump()); + } + + public function testLoggerDelegatesClearingEntries() + { + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->clear() + ); + + $plugin = $this->_createPlugin($logger); + $plugin->clear(); + } + + public function testCommandIsSentToLogger() + { + $evt = $this->_createCommandEvent("foo\r\n"); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(pattern('~foo\r\n~')) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->commandSent($evt); + } + + public function testResponseIsSentToLogger() + { + $evt = $this->_createResponseEvent("354 Go ahead\r\n"); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(pattern('~354 Go ahead\r\n~')) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->responseReceived($evt); + } + + public function testTransportBeforeStartChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->beforeTransportStarted($evt); + } + + public function testTransportStartChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->transportStarted($evt); + } + + public function testTransportStopChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->transportStopped($evt); + } + + public function testTransportBeforeStopChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->beforeTransportStopped($evt); + } + + public function testExceptionsArePassedToDelegateAndLeftToBubbleUp() + { + $transport = $this->_createTransport(); + $evt = $this->_createTransportExceptionEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + -> allowing($logger) + ); + + $plugin = $this->_createPlugin($logger); + try + { + $plugin->exceptionThrown($evt); + $this->fail('Exception should bubble up.'); + } + catch (Swift_TransportException $ex) + { + } + } + + // -- Creation Methods + + private function _createLogger() + { + return $this->_mock('Swift_Plugins_Logger'); + } + + private function _createPlugin($logger) + { + return new Swift_Plugins_LoggerPlugin($logger); + } + + private function _createCommandEvent($command) + { + $evt = $this->_mock('Swift_Events_CommandEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getCommand() -> returns($command) + -> ignoring($evt) + ); + return $evt; + } + + private function _createResponseEvent($response) + { + $evt = $this->_mock('Swift_Events_ResponseEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getResponse() -> returns($response) + -> ignoring($evt) + ); + return $evt; + } + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createTransportChangeEvent() + { + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getSource() -> returns($this->_createTransport()) + -> ignoring($evt) + ); + return $evt; + } + + private function _createTransportExceptionEvent() + { + $evt = $this->_mock('Swift_Events_TransportExceptionEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getException() -> returns(new Swift_TransportException('')) + -> ignoring($evt) + ); + return $evt; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php b/lib/Swift/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php new file mode 100644 index 0000000..6c6f7bb --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php @@ -0,0 +1,71 @@ +add(">> Foo\r\n"); + $this->assertEqual(">> Foo\r\n", $logger->dump()); + } + + public function testAddingMultipleEntriesDumpsMultipleLines() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEqual( + ">> FOO\r\n" . PHP_EOL . + "<< 502 That makes no sense\r\n" . PHP_EOL . + ">> RSET\r\n" . PHP_EOL . + "<< 250 OK\r\n", + $logger->dump() + ); + } + + public function testLogCanBeCleared() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEqual( + ">> FOO\r\n" . PHP_EOL . + "<< 502 That makes no sense\r\n" . PHP_EOL . + ">> RSET\r\n" . PHP_EOL . + "<< 250 OK\r\n", + $logger->dump() + ); + + $logger->clear(); + + $this->assertEqual('', $logger->dump()); + } + + public function testLengthCanBeTruncated() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(2); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEqual( + ">> RSET\r\n" . PHP_EOL . + "<< 250 OK\r\n", + $logger->dump(), + '%s: Log should be truncated to last 2 entries' + ); + } + +} \ No newline at end of file diff --git a/lib/Swift/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php b/lib/Swift/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php new file mode 100644 index 0000000..6aaafab --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php @@ -0,0 +1,30 @@ +add(">> Foo"); + $data = ob_get_clean(); + + $this->assertEqual(">> Foo" . PHP_EOL, $data); + } + + public function testAddingEntryDumpsEscapedLineWithHtml() + { + $logger = new Swift_Plugins_Loggers_EchoLogger(true); + ob_start(); + $logger->add(">> Foo"); + $data = ob_get_clean(); + + $this->assertEqual(">> Foo
" . PHP_EOL, $data); + } + +} \ No newline at end of file diff --git a/lib/Swift/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php b/lib/Swift/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php new file mode 100644 index 0000000..da34679 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php @@ -0,0 +1,115 @@ +_createConnection(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $this->_checking(Expectations::create() + -> one($connection)->connect() + -> ignoring($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginDisconnectsFromPop3HostBeforeTransportStarts() + { + $connection = $this->_createConnection(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $this->_checking(Expectations::create() + -> one($connection)->disconnect() + -> ignoring($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginDoesNotConnectToSmtpIfBoundToDifferentTransport() + { + $connection = $this->_createConnection(); + + $smtp = $this->_createTransport(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + $plugin->bindSmtp($smtp); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $this->_checking(Expectations::create() + -> never($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginCanBindToSpecificTransport() + { + $connection = $this->_createConnection(); + + $smtp = $this->_createTransport(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + $plugin->bindSmtp($smtp); + + $evt = $this->_createTransportChangeEvent($smtp); + + $this->_checking(Expectations::create() + -> one($connection)->connect() + -> ignoring($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + // -- Creation Methods + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createTransportChangeEvent($transport) + { + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getSource() -> returns($transport) + -> ignoring($evt)->getTransport() -> returns($transport) + ); + return $evt; + } + + public function _createConnection() + { + return $this->_mock('Swift_Plugins_Pop_Pop3Connection'); + } + + public function _createPlugin($host, $port, $crypto = null) + { + return new Swift_Plugins_PopBeforeSmtpPlugin($host, $port, $crypto); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Plugins/ReporterPluginTest.php b/lib/Swift/tests/unit/Swift/Plugins/ReporterPluginTest.php new file mode 100644 index 0000000..546c256 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/ReporterPluginTest.php @@ -0,0 +1,122 @@ +_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar.tld' => 'Foo')) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array()) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedTo() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'foo@bar.tld' => 'Foo', 'zip@button' => 'Zip' + )) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array('zip@button')) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> one($reporter)->notify($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedCc() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'foo@bar.tld' => 'Foo' + )) + -> allowing($message)->getCc() -> returns(array( + 'zip@button' => 'Zip', 'test@test.com' => 'Test' + )) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array('zip@button')) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> one($reporter)->notify($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL) + -> one($reporter)->notify($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedBcc() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'foo@bar.tld' => 'Foo' + )) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip', 'test@test.com' => 'Test' + )) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array('zip@button')) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> one($reporter)->notify($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL) + -> one($reporter)->notify($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + // -- Creation Methods + + private function _createMessage() + { + return $this->_mock('Swift_Mime_Message'); + } + + private function _createSendEvent() + { + return $this->_mock('Swift_Events_SendEvent'); + } + + private function _createReporter() + { + return $this->_mock('Swift_Plugins_Reporter'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php b/lib/Swift/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php new file mode 100644 index 0000000..13ff3b7 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php @@ -0,0 +1,71 @@ +_hitReporter = new Swift_Plugins_Reporters_HitReporter(); + $this->_message = $this->_mock('Swift_Mime_Message'); + } + + public function testReportingFail() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEqual(array('foo@bar.tld'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testMultipleReports() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEqual(array('foo@bar.tld', 'zip@button'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testReportingPassIsIgnored() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_PASS + ); + $this->assertEqual(array('foo@bar.tld'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testBufferCanBeCleared() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEqual(array('foo@bar.tld', 'zip@button'), + $this->_hitReporter->getFailedRecipients() + ); + $this->_hitReporter->clear(); + $this->assertEqual(array(), $this->_hitReporter->getFailedRecipients()); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php b/lib/Swift/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php new file mode 100644 index 0000000..8b886d8 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php @@ -0,0 +1,61 @@ +_html = new Swift_Plugins_Reporters_HtmlReporter(); + $this->_message = $this->_mock('Swift_Mime_Message'); + } + + public function testReportingPass() + { + ob_start(); + $this->_html->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_PASS + ); + $html = ob_get_clean(); + + $this->assertPattern('~ok|pass~i', $html, '%s: Reporter should indicate pass'); + $this->assertPattern('~foo@bar\.tld~', $html, '%s: Reporter should show address'); + } + + public function testReportingFail() + { + ob_start(); + $this->_html->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $html = ob_get_clean(); + + $this->assertPattern('~fail~i', $html, '%s: Reporter should indicate fail'); + $this->assertPattern('~zip@button~', $html, '%s: Reporter should show address'); + } + + public function testMultipleReports() + { + ob_start(); + $this->_html->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_PASS + ); + $this->_html->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $html = ob_get_clean(); + + $this->assertPattern('~ok|pass~i', $html, '%s: Reporter should indicate pass'); + $this->assertPattern('~foo@bar\.tld~', $html, '%s: Reporter should show address'); + $this->assertPattern('~fail~i', $html, '%s: Reporter should indicate fail'); + $this->assertPattern('~zip@button~', $html, '%s: Reporter should show address'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Plugins/ThrottlerPluginTest.php b/lib/Swift/tests/unit/Swift/Plugins/ThrottlerPluginTest.php new file mode 100644 index 0000000..7299197 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Plugins/ThrottlerPluginTest.php @@ -0,0 +1,129 @@ +_createSleeper(); + $timer = $this->_createTimer(); + + //10MB/min + $plugin = new Swift_Plugins_ThrottlerPlugin( + 10000000, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE, + $sleeper, $timer + ); + + $this->_checking(Expectations::create() + -> one($timer)->getTimestamp() -> returns(0) + -> one($timer)->getTimestamp() -> returns(1) //expected 0.6 + -> one($timer)->getTimestamp() -> returns(1) //expected 1.2 (sleep 1) + -> one($timer)->getTimestamp() -> returns(2) //expected 1.8 + -> one($timer)->getTimestamp() -> returns(2) //expected 2.4 (sleep 1) + -> ignoring($timer) + + -> exactly(2)->of($sleeper)->sleep(1) + ); + + //10,000,000 bytes per minute + //100,000 bytes per email + + // .: (10,000,000/100,000)/60 emails per second = 1.667 emais/sec + + $message = $this->_createMessageWithByteCount(100000); //100KB + + $evt = $this->_createSendEvent($message); + + for ($i = 0; $i < 5; ++$i) + { + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + } + + public function testMessagesPerMinuteThrottling() + { + $sleeper = $this->_createSleeper(); + $timer = $this->_createTimer(); + + //60/min + $plugin = new Swift_Plugins_ThrottlerPlugin( + 60, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE, + $sleeper, $timer + ); + + $this->_checking(Expectations::create() + -> one($timer)->getTimestamp() -> returns(0) + -> one($timer)->getTimestamp() -> returns(0) //expected 1 (sleep 1) + -> one($timer)->getTimestamp() -> returns(2) //expected 2 + -> one($timer)->getTimestamp() -> returns(2) //expected 3 (sleep 1) + -> one($timer)->getTimestamp() -> returns(4) //expected 4 + -> ignoring($timer) + + -> exactly(2)->of($sleeper)->sleep(1) + ); + + //60 messages per minute + //1 message per second + + $message = $this->_createMessageWithByteCount(10); + + $evt = $this->_createSendEvent($message); + + for ($i = 0; $i < 5; ++$i) + { + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + } + + // -- Creation Methods + + private function _createSleeper() + { + return $this->_mock('Swift_Plugins_Sleeper'); + } + + private function _createTimer() + { + return $this->_mock('Swift_Plugins_Timer'); + } + + private function _createMessageWithByteCount($bytes) + { + $this->_bytes = $bytes; + $msg = $this->_mock('Swift_Mime_Message'); + $this->_checking(Expectations::create() + -> ignoring($msg)->toByteStream(any()) -> calls(array($this, '_write')) + ); + return $msg; + } + + private function _createSendEvent($message) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getMessage() -> returns($message) + ); + return $evt; + } + + private $_bytes = 0; + public function _write($invocation) + { + $args = $invocation->getArguments(); + $is = $args[0]; + for ($i = 0; $i < $this->_bytes; ++$i) + { + $is->write('x'); + } + } + +} diff --git a/lib/Swift/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php b/lib/Swift/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php new file mode 100644 index 0000000..d9e4b57 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php @@ -0,0 +1,138 @@ +_createFilter(array(0x61, 0x62), array(0x63, 0x64)); + $this->assertEqual( + array(0x59, 0x60, 0x63, 0x64, 0x65), + $filter->filter(array(0x59, 0x60, 0x61, 0x62, 0x65)) + ); + } + + public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer() + { + $filter = $this->_createFilter(array(0x61, 0x62), array(0x63, 0x64)); + $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)), + '%s: Filter should buffer since 0x61 0x62 is the needle and the ending ' . + '0x61 could be from 0x61 0x62' + ); + } + + public function testFilterCanMakeMultipleReplacements() + { + $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(0x63)); + $this->assertEqual( + array(0x60, 0x63, 0x60, 0x63, 0x60), + $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60)) + ); + } + + public function testMultipleReplacementsCanBeDifferent() + { + $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(array(0x63), array(0x64))); + $this->assertEqual( + array(0x60, 0x63, 0x60, 0x64, 0x60), + $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60)) + ); + } + + public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString() + { + $filter = $this->_createFilter(array(0x0D, 0x0A), array(0x0A)); + $this->assertFalse($filter->shouldBuffer(array(0x61, 0x62, 0x0D, 0x0A, 0x63)), + '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF' + ); + } + + public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString() + { + $filter = $this->_createFilter(array(array(0x61, 0x62), array(0x63)), array(0x64)); + $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)), + '%s: Filter should buffer since 0x61 0x62 is a needle and the ending ' . + '0x61 could be from 0x61 0x62' + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsLF() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x61, 0x0A, 0x62, 0x0A, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsCR() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0D, 0x61, 0x0D, 0x62, 0x0D, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsCRLF() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsLFCR() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x0D, 0x61, 0x0A, 0x0D, 0x62, 0x0A, 0x0D, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputContainsLFLF() + { + //Lighthouse Bug #23 + + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x0A, 0x61, 0x0A, 0x0A, 0x62, 0x0A, 0x0A, 0x63)) + ); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_ByteArrayReplacementFilter($search, $replace); + } + +} + diff --git a/lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php b/lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php new file mode 100644 index 0000000..0d119c4 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php @@ -0,0 +1,44 @@ +_createFactory(); + $this->assertIsA($factory->createFilter('a', 'b'), + 'Swift_StreamFilters_StringReplacementFilter' + ); + } + + public function testSameInstancesAreCached() + { + $factory = $this->_createFactory(); + $filter1 = $factory->createFilter('a', 'b'); + $filter2 = $factory->createFilter('a', 'b'); + $this->assertSame($filter1, $filter2, '%s: Instances should be cached'); + } + + public function testDifferingInstancesAreNotCached() + { + $factory = $this->_createFactory(); + $filter1 = $factory->createFilter('a', 'b'); + $filter2 = $factory->createFilter('a', 'c'); + $this->assertNotEqual($filter1, $filter2, + '%s: Differing instances should not be cached' + ); + } + + // -- Creation methods + + private function _createFactory() + { + return new Swift_StreamFilters_StringReplacementFilterFactory(); + } + +} + diff --git a/lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php b/lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php new file mode 100644 index 0000000..81cba67 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php @@ -0,0 +1,62 @@ +_createFilter('foo', 'bar'); + $this->assertEqual('XbarYbarZ', $filter->filter('XfooYfooZ')); + } + + public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer() + { + $filter = $this->_createFilter('foo', 'bar'); + $this->assertTrue($filter->shouldBuffer('XfooYf'), + '%s: Filter should buffer since "foo" is the needle and the ending ' . + '"f" could be from "foo"' + ); + } + + public function testFilterCanMakeMultipleReplacements() + { + $filter = $this->_createFilter(array('a', 'b'), 'foo'); + $this->assertEqual('XfooYfooZ', $filter->filter('XaYbZ')); + } + + public function testMultipleReplacementsCanBeDifferent() + { + $filter = $this->_createFilter(array('a', 'b'), array('foo', 'zip')); + $this->assertEqual('XfooYzipZ', $filter->filter('XaYbZ')); + } + + public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString() + { + $filter = $this->_createFilter("\r\n", "\n"); + $this->assertFalse($filter->shouldBuffer("foo\r\nbar"), + '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF' + ); + } + + public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString() + { + $filter = $this->_createFilter(array('foo', 'zip'), 'bar'); + $this->assertTrue($filter->shouldBuffer('XfooYzi'), + '%s: Filter should buffer since "zip" is a needle and the ending ' . + '"zi" could be from "zip"' + ); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + +} + diff --git a/lib/Swift/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php b/lib/Swift/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php new file mode 100644 index 0000000..aac3bff --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php @@ -0,0 +1,383 @@ +_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $listener = $this->_mock('Swift_Events_EventListener'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> one($dispatcher)->bindEventListener($listener) + ); + $smtp->registerPlugin($listener); + } + + public function testSendingDispatchesBeforeSendEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $message = $this->_createMessage(); + $smtp = $this->_getTransport($buf, $dispatcher); + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($dispatcher)->createSendEvent(optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeSendPerformed') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message)); + } + + public function testSendingDispatchesSendEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $message = $this->_createMessage(); + $smtp = $this->_getTransport($buf, $dispatcher); + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($dispatcher)->createSendEvent(optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'sendPerformed') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message)); + } + + public function testSendEventCapturesFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("500 Not now\r\n") + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setFailedRecipients(array('mark@swiftmailer.org')) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message)); + } + + public function testSendEventHasResultFailedIfAllFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("500 Not now\r\n") + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setResult(Swift_Events_SendEvent::RESULT_FAILED) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message)); + } + + public function testSendEventHasResultTentativeIfSomeFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array( + 'mark@swiftmailer.org'=>'Mark', 'chris@site.tld'=>'Chris' + )) + -> ignoring($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("500 Not now\r\n") + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message)); + } + + public function testSendEventHasResultSuccessIfNoFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array( + 'mark@swiftmailer.org'=>'Mark', 'chris@site.tld'=>'Chris' + )) + -> ignoring($message) + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setResult(Swift_Events_SendEvent::RESULT_SUCCESS) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(2, $smtp->send($message)); + } + + public function testCancellingEventBubbleBeforeSendStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeSendPerformed') + -> ignoring($dispatcher) + -> atLeast(1)->of($evt)->bubbleCancelled() -> returns(true) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message)); + } + + public function testStartingTransportDispatchesTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'transportStarted') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testStartingTransportDispatchesBeforeTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStarted') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testCancellingBubbleBeforeTransportStartStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStarted') + -> allowing($evt)->bubbleCancelled() -> returns(true) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + + $this->assertFalse($smtp->isStarted(), + '%s: Transport should not be started since event bubble was cancelled' + ); + } + + public function testStoppingTransportDispatchesTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'transportStopped') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + } + + public function testStoppingTransportDispatchesBeforeTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStopped') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + } + + public function testCancellingBubbleBeforeTransportStoppedStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $seq = $this->_sequence('stopping transport'); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStopped') -> inSequence($seq) + -> allowing($evt)->bubbleCancelled() -> inSequence($seq) -> returns(true) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + + $this->assertTrue($smtp->isStarted(), + '%s: Transport should not be stopped since event bubble was cancelled' + ); + } + + public function testResponseEventsAreGenerated() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_ResponseEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createResponseEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'responseReceived') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testCommandEventsAreGenerated() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_CommandEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createCommandEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'commandSent') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testExceptionsCauseExceptionEvents() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportExceptionEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> atLeast(1)->of($buf)->readLine(any()) -> returns("503 I'm sleepy, go away!\r\n") + -> allowing($dispatcher)->createTransportExceptionEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'exceptionThrown') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + $this->fail('TransportException should be thrown on invalid response'); + } + catch (Swift_TransportException $e) + { + } + } + + public function testExceptionBubblesCanBeCancelled() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportExceptionEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> atLeast(1)->of($buf)->readLine(any()) -> returns("503 I'm sleepy, go away!\r\n") + -> allowing($dispatcher)->createTransportExceptionEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'exceptionThrown') + -> atLeast(1)->of($evt)->bubbleCancelled() -> returns(true) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + // -- Creation Methods + + protected function _createEventDispatcher($stub = true) + { + return $stub + ? $this->_stub('Swift_Events_EventDispatcher') + : $this->_mock('Swift_Events_EventDispatcher') + ; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/AbstractSmtpTest.php b/lib/Swift/tests/unit/Swift/Transport/AbstractSmtpTest.php new file mode 100644 index 0000000..5c8478a --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/AbstractSmtpTest.php @@ -0,0 +1,962 @@ +_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + ); + $this->_finishBuffer($buf); + try + { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->assertTrue($smtp->isStarted(), '%s: start() should have started connection'); + } + catch (Exception $e) + { + $this->fail('220 is a valid SMTP greeting and should be accepted'); + } + } + + public function testBadGreetingCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("554 I'm busy\r\n") + ); + $this->_finishBuffer($buf); + try + { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('554 greeting indicates an error and should cause an exception'); + } + catch (Exception $e) + { + $this->assertFalse($smtp->isStarted(), '%s: start() should have failed'); + } + } + + public function testStartSendsHeloToInitiate() + { + /* -- RFC 2821, 3.2. + + 3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + + -- RFC 2281, 4.1.1.1. + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + -- RFC 2821, 4.3.2. + + EHLO or HELO + S: 250 + E: 504, 550 + + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^HELO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + } + catch (Exception $e) + { + $this->fail('Starting SMTP should send HELO and accept 250 response'); + } + } + + public function testInvalidHeloResponseCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^HELO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('504 WTF' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('Non 250 HELO response should raise Exception'); + } + catch (Exception $e) + { + $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed'); + } + } + + public function testDomainNameIsPlacedInHelo() + { + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write("HELO mydomain.com\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testSuccessfulMailCommand() + { + /* -- RFC 2821, 3.3. + + There are three steps to SMTP mail transactions. The transaction + starts with a MAIL command which gives the sender identification. + + ..... + + The first step in the procedure is the MAIL command. + + MAIL FROM: [SP ] + + -- RFC 2821, 4.1.1.2. + + Syntax: + + "MAIL FROM:" ("<>" / Reverse-Path) + [SP Mail-parameters] CRLF + -- RFC 2821, 4.1.2. + + Reverse-path = Path + Forward-path = Path + Path = "<" [ A-d-l ":" ] Mailbox ">" + A-d-l = At-domain *( "," A-d-l ) + ; Note that this form, the so-called "source route", + ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be + ; ignored. + At-domain = "@" domain + + -- RFC 2821, 4.3.2. + + MAIL + S: 250 + E: 552, 451, 452, 550, 553, 503 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + $smtp->send($message); + } + catch (Exception $e) + { + $this->fail('MAIL FROM should accept a 250 response'); + } + } + + public function testInvalidResponseCodeFromMailCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('553 Bad' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + $smtp->send($message); + $this->fail('MAIL FROM should accept a 250 response'); + } + catch (Exception $e) + { + } + } + + public function testSenderIsPreferredOverFrom() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getSender() -> returns(array('another@domain.com'=>'Someone')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testReturnPathIsPreferredOverSender() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getSender() -> returns(array('another@domain.com'=>'Someone')) + -> allowing($message)->getReturnPath() -> returns('more@domain.com') + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testSuccessfulRcptCommandWith250Response() + { + /* -- RFC 2821, 3.3. + + The second step in the procedure is the RCPT command. + + RCPT TO: [ SP ] + + The first or only argument to this command includes a forward-path + (normally a mailbox and domain, always surrounded by "<" and ">" + brackets) identifying one recipient. If accepted, the SMTP server + returns a 250 OK reply and stores the forward-path. If the recipient + is known not to be a deliverable address, the SMTP server returns a + 550 reply, typically with a string such as "no such user - " and the + mailbox name (other circumstances and reply codes are possible). + This step of the procedure can be repeated any number of times. + + -- RFC 2821, 4.1.1.3. + + This command is used to identify an individual recipient of the mail + data; multiple recipients are specified by multiple use of this + command. The argument field contains a forward-path and may contain + optional parameters. + + The forward-path normally consists of the required destination + mailbox. Sending systems SHOULD not generate the optional list of + hosts known as a source route. + + ....... + + "RCPT TO:" ("" / "" / Forward-Path) + [SP Rcpt-parameters] CRLF + + -- RFC 2821, 4.2.2. + + 250 Requested mail action okay, completed + 251 User not local; will forward to + (See section 3.4) + 252 Cannot VRFY user, but will accept message and attempt + delivery + + -- RFC 2821, 4.3.2. + + RCPT + S: 250, 251 (but see section 3.4 for discussion of 251 and 551) + E: 550, 551, 552, 553, 450, 451, 452, 503, 550 + */ + + //We'll treat 252 as accepted since it isn't really a failure + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('SMTP-envelope'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + $smtp->send($message); + } + catch (Exception $e) + { + $this->fail('RCPT TO should accept a 250 response'); + } + } + + public function testMailFromCommandIsOnlySentOncePerMessage() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('SMTP-envelope'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + -> never($buf)->write("MAIL FROM: \r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testMultipleRecipientsSendsMultipleRcpt() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array( + 'foo@bar' => null, + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testCcRecipientsSendsMultipleRcpt() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getCc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testSendReturnsNumberOfSuccessfulRecipients() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getCc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('501 Nobody here' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(2, $smtp->send($message), + '%s: 1 of 3 recipients failed so 2 should be returned' + ); + } + + public function testRsetIsSentIfNoSuccessfulRecipients() + { + /* --RFC 2821, 4.1.1.5. + + This command specifies that the current mail transaction will be + aborted. Any stored sender, recipients, and mail data MUST be + discarded, and all buffers and state tables cleared. The receiver + MUST send a "250 OK" reply to a RSET command with no arguments. A + reset command may be issued by the client at any time. + + -- RFC 2821, 4.3.2. + + RSET + S: 250 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('503 Bad' . "\r\n") + -> one($buf)->write("RSET\r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message), + '%s: 1 of 1 recipients failed so 0 should be returned' + ); + } + + public function testSuccessfulDataCommand() + { + /* -- RFC 2821, 3.3. + + The third step in the procedure is the DATA command (or some + alternative specified in a service extension). + + DATA + + If accepted, the SMTP server returns a 354 Intermediate reply and + considers all succeeding lines up to but not including the end of + mail data indicator to be the message text. + + -- RFC 2821, 4.1.1.4. + + The receiver normally sends a 354 response to DATA, and then treats + the lines (strings ending in sequences, as described in + section 2.3.7) following the command as mail data from the sender. + This command causes the mail data to be appended to the mail data + buffer. The mail data may contain any of the 128 ASCII character + codes, although experience has indicated that use of control + characters other than SP, HT, CR, and LF may cause problems and + SHOULD be avoided when possible. + + -- RFC 2821, 4.3.2. + + DATA + I: 354 -> data -> S: 250 + E: 552, 554, 451, 452 + E: 451, 554, 503 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message) + + -> one($buf)->write("DATA\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('354 Go ahead' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + $smtp->send($message); + } + catch (Exception $e) + { + $this->fail('354 is the expected response to DATA'); + } + } + + public function testBadDataResponseCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message) + + -> one($buf)->write("DATA\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('451 Bad' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + $smtp->send($message); + $this->fail('354 is the expected response to DATA (not observed)'); + } + catch (Exception $e) + { + } + } + + public function testMessageIsStreamedToBufferForData() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('DATA Streaming'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + + -> one($buf)->write("DATA\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('354 OK' . "\r\n") + -> one($message)->toByteStream($buf) -> inSequence($s) + -> one($buf)->write("\r\n.\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + + -> allowing($message) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testBadResponseAfterDataTransmissionCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('DATA Streaming'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + + -> one($buf)->write("DATA\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('354 OK' . "\r\n") + -> one($message)->toByteStream($buf) -> inSequence($s) + -> one($buf)->write("\r\n.\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('554 Error' . "\r\n") + + -> allowing($message) + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + $smtp->send($message); + $this->fail('250 is the expected response after a DATA transmission (not observed)'); + } + catch (Exception $e) + { + } + } + + public function testBccRecipientsAreRemovedFromHeaders() + { + /* -- RFC 2821, 7.2. + + Addresses that do not appear in the message headers may appear in the + RCPT commands to an SMTP server for a number of reasons. The two + most common involve the use of a mailing address as a "list exploder" + (a single address that resolves into multiple addresses) and the + appearance of "blind copies". Especially when more than one RCPT + command is present, and in order to avoid defeating some of the + purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy + the full set of RCPT command arguments into the headers, either as + part of trace headers or as informational or private-extension + headers. Since this rule is often violated in practice, and cannot + be enforced, sending SMTP systems that are aware of "bcc" use MAY + find it helpful to send each blind copy as a separate message + transaction containing only a single RCPT command. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> atLeast(1)->of($message)->setBcc(array()) + -> allowing($message) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testEachBccRecipientIsSentASeparateMessage() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> atLeast(1)->of($message)->setBcc(array()) + -> one($message)->setBcc(array('zip@button' => 'Zip Button')) + -> one($message)->setBcc(array('test@domain' => 'Test user')) + -> atLeast(1)->of($message)->setBcc(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(4) + -> one($buf)->readLine(4) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(5) + -> one($buf)->readLine(5) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(6) + -> one($buf)->readLine(6) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(7) + -> one($buf)->readLine(7) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(8) + -> one($buf)->readLine(8) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(9) + -> one($buf)->readLine(9) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(10) + -> one($buf)->readLine(10) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(11) + -> one($buf)->readLine(11) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(12) + -> one($buf)->readLine(12) -> returns("250 OK\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(3, $smtp->send($message)); + } + + public function testMessageStateIsRestoredOnFailure() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> one($message)->setBcc(array()) + -> one($message)->setBcc(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns("451 No\r\n") + ); + $this->_finishBuffer($buf); + + $smtp->start(); + try + { + $smtp->send($message); + $this->fail('A bad response was given so exception is expected'); + } + catch (Exception $e) + { + } + } + + public function testStopSendsQuitCommand() + { + /* -- RFC 2821, 4.1.1.10. + + This command specifies that the receiver MUST send an OK reply, and + then close the transmission channel. + + The receiver MUST NOT intentionally close the transmission channel + until it receives and replies to a QUIT command (even if there was an + error). The sender MUST NOT intentionally close the transmission + channel until it sends a QUIT command and SHOULD wait until it + receives the reply (even if there was an error response to a previous + command). If the connection is closed prematurely due to violations + of the above or system or network failure, the server MUST cancel any + pending transaction, but not undo any previously completed + transaction, and generally MUST act as if the command or transaction + in progress had received a temporary error (i.e., a 4yz response). + + The QUIT command may be issued at any time. + + Syntax: + "QUIT" CRLF + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($buf)->initialize() + -> one($buf)->write("QUIT\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("221 Bye\r\n") + -> one($buf)->terminate() + ); + $this->_finishBuffer($buf); + + $this->assertFalse($smtp->isStarted()); + $smtp->start(); + $this->assertTrue($smtp->isStarted()); + $smtp->stop(); + $this->assertFalse($smtp->isStarted()); + } + + public function testBufferCanBeFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ref = $smtp->getBuffer(); + $this->assertReference($buf, $ref); + } + + public function testBufferCanBeWrittenToUsingExecuteCommand() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($buf)->write("FOO\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> ignoring($buf) + ); + + $res = $smtp->executeCommand("FOO\r\n"); + $this->assertEqual("250 OK\r\n", $res); + } + + public function testResponseCodesAreValidated() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($buf)->write("FOO\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("551 Not ok\r\n") + -> ignoring($buf) + ); + + try + { + $smtp->executeCommand("FOO\r\n", array(250, 251)); + $this->fail('A 250 or 251 response was needed but 551 was returned.'); + } + catch (Exception $e) + { + } + } + + public function testFailedRecipientsCanBeCollectedByReference() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> atLeast(1)->of($message)->setBcc(array()) + -> one($message)->setBcc(array('zip@button' => 'Zip Button')) + -> one($message)->setBcc(array('test@domain' => 'Test user')) + -> atLeast(1)->of($message)->setBcc(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(4) + -> one($buf)->readLine(4) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(5) + -> one($buf)->readLine(5) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(6) + -> one($buf)->readLine(6) -> returns("500 Bad\r\n") + -> one($buf)->write("RSET\r\n") -> returns(7) + -> one($buf)->readLine(7) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(8) + -> one($buf)->readLine(8) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(9) + -> one($buf)->readLine(9) -> returns("500 Bad\r\n") + -> one($buf)->write("RSET\r\n") -> returns(10) + -> one($buf)->readLine(10) -> returns("250 OK\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message, $failures)); + $this->assertEqual(array('zip@button', 'test@domain'), $failures, + '%s: Failures should be caught in an array' + ); + } + + public function testSendingRegeneratesMessageId() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> one($message)->generateId() + -> allowing($message) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + // -- Protected methods + + protected function _getBuffer() + { + return $this->_mock('Swift_Transport_IoBuffer'); + } + + protected function _createMessage() + { + return $this->_mock('Swift_Mime_Message'); + } + + protected function _finishBuffer($buf) + { + $this->_checking(Expectations::create() + -> ignoring($buf)->readLine(0) -> returns('220 server.com foo' . "\r\n") + -> ignoring($buf)->write(pattern('~^(EH|HE)LO .*?\r\n$~D')) -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 ServerName' . "\r\n") + -> ignoring($buf)->write(pattern('~^MAIL FROM: <.*?>\r\n$~D')) -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 OK' . "\r\n") + -> ignoring($buf)->write(pattern('~^RCPT TO: <.*?>\r\n$~D')) -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 OK' . "\r\n") + -> ignoring($buf)->write("DATA\r\n") -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('354 OK' . "\r\n") + -> ignoring($buf)->write("\r\n.\r\n") -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 OK' . "\r\n") + -> ignoring($buf)->write("RSET\r\n") -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns("250 OK\r\n") + -> ignoring($buf) -> returns(false) + ); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php b/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php new file mode 100644 index 0000000..1b3550d --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php @@ -0,0 +1,69 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsCramMd5() + { + /* -- RFC 2195, 2. + The authentication type associated with CRAM is "CRAM-MD5". + */ + + $cram = $this->_getAuthenticator(); + $this->assertEqual('CRAM-MD5', $cram->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + $cram = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH CRAM-MD5\r\n", array(334)) + -> returns('334 ' . base64_encode('') . "\r\n") + // The use of any() is controversial, but here to avoid crazy test logic + -> one($this->_agent)->executeCommand(any(), array(235)) + ); + + $this->assertTrue($cram->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $cram = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH CRAM-MD5\r\n", array(334)) + -> returns('334 ' . base64_encode('') . "\r\n") + // The use of any() is controversial, but here to avoid crazy test logic + -> one($this->_agent)->executeCommand(any(), array(235)) + -> throws(new Swift_TransportException("")) + + -> one($this->_agent)->executeCommand("RSET\r\n", array(250)) + ); + + $this->assertFalse($cram->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_CramMd5Authenticator(); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php b/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php new file mode 100644 index 0000000..c3e5596 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php @@ -0,0 +1,62 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsLogin() + { + $login = $this->_getAuthenticator(); + $this->assertEqual('LOGIN', $login->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + $login = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH LOGIN\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('jack') . "\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('pass') . "\r\n", array(235)) + ); + + $this->assertTrue($login->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $login = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH LOGIN\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('jack') . "\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('pass') . "\r\n", array(235)) + -> throws(new Swift_TransportException("")) + -> one($this->_agent)->executeCommand("RSET\r\n", array(250)) + ); + + $this->assertFalse($login->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_LoginAuthenticator(); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php b/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php new file mode 100644 index 0000000..8091b09 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php @@ -0,0 +1,73 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsPlain() + { + /* -- RFC 4616, 1. + The name associated with this mechanism is "PLAIN". + */ + + $login = $this->_getAuthenticator(); + $this->assertEqual('PLAIN', $login->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + /* -- RFC 4616, 2. + The client presents the authorization identity (identity to act as), + followed by a NUL (U+0000) character, followed by the authentication + identity (identity whose password will be used), followed by a NUL + (U+0000) character, followed by the clear-text password. + */ + + $plain = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand('AUTH PLAIN ' . base64_encode( + 'jack' . chr(0) . 'jack' . chr(0) . 'pass' + ) . "\r\n", array(235)) + ); + + $this->assertTrue($plain->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $plain = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand('AUTH PLAIN ' . base64_encode( + 'jack' . chr(0) . 'jack' . chr(0) . 'pass' + ) . "\r\n", array(235)) -> throws(new Swift_TransportException("")) + + -> one($this->_agent)->executeCommand("RSET\r\n", array(250)) + ); + + $this->assertFalse($plain->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_PlainAuthenticator(); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php b/lib/Swift/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php new file mode 100644 index 0000000..7d2d0e7 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php @@ -0,0 +1,158 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsAuth() + { + $auth = $this->_createHandler(array()); + $this->assertEqual('AUTH', $auth->getHandledKeyword()); + } + + public function testUsernameCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setUsername('jack'); + $this->assertEqual('jack', $auth->getUsername()); + } + + public function testPasswordCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setPassword('pass'); + $this->assertEqual('pass', $auth->getPassword()); + } + + public function testAuthModeCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setAuthMode('PLAIN'); + $this->assertEqual('PLAIN', $auth->getAuthMode()); + } + + public function testMixinMethods() + { + $auth = $this->_createHandler(array()); + $mixins = $auth->exposeMixinMethods(); + $this->assertTrue(in_array('getUsername', $mixins), + '%s: getUsername() should be accessible via mixin' + ); + $this->assertTrue(in_array('setUsername', $mixins), + '%s: setUsername() should be accessible via mixin' + ); + $this->assertTrue(in_array('getPassword', $mixins), + '%s: getPassword() should be accessible via mixin' + ); + $this->assertTrue(in_array('setPassword', $mixins), + '%s: setPassword() should be accessible via mixin' + ); + $this->assertTrue(in_array('setAuthMode', $mixins), + '%s: setAuthMode() should be accessible via mixin' + ); + $this->assertTrue(in_array('getAuthMode', $mixins), + '%s: getAuthMode() should be accessible via mixin' + ); + } + + public function testAuthenticatorsAreCalledAccordingToParamsAfterEhlo() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $this->_checking(Expectations::create() + -> never($a1)->authenticate($this->_agent, 'jack', 'pass') + -> one($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + ); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testAuthenticatorsAreNotUsedIfNoUsernameSet() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $this->_checking(Expectations::create() + -> never($a1)->authenticate($this->_agent, 'jack', 'pass') + -> never($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + ); + + $auth = $this->_createHandler(array($a1, $a2)); + + $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testSeveralAuthenticatorsAreTriedIfNeeded() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $this->_checking(Expectations::create() + -> one($a1)->authenticate($this->_agent, 'jack', 'pass') -> returns(false) + -> one($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + ); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('PLAIN', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testFirstAuthenticatorToPassBreaksChain() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + $a3 = $this->_createMockAuthenticator('CRAM-MD5'); + + $this->_checking(Expectations::create() + -> one($a1)->authenticate($this->_agent, 'jack', 'pass') -> returns(false) + -> one($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + -> never($a3)->authenticate($this->_agent, 'jack', 'pass') + ); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('PLAIN', 'LOGIN', 'CRAM-MD5')); + $auth->afterEhlo($this->_agent); + } + + // -- Private helpers + + private function _createHandler($authenticators) + { + return new Swift_Transport_Esmtp_AuthHandler($authenticators); + } + + private function _createMockAuthenticator($type) + { + $authenticator = $this->_mock('Swift_Transport_Esmtp_Authenticator'); + $this->_checking(Expectations::create() + -> ignoring($authenticator)->getAuthKeyword() -> returns($type) + ); + return $authenticator; + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php b/lib/Swift/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php new file mode 100644 index 0000000..c52b631 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php @@ -0,0 +1,314 @@ +_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->getPriorityOver('STARTTLS') -> returns(0) + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> allowing($ext2)->getPriorityOver('AUTH') -> returns(-1) + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $this->assertEqual(array($ext2, $ext1), $smtp->getExtensionHandlers()); + } + + public function testHandlersAreNotifiedOfParams() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->setKeywordParams(array('PLAIN', 'LOGIN')) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> allowing($ext2)->setKeywordParams(array('123456')) + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $smtp->start(); + } + + public function testSupportedExtensionHandlersAreRunAfterEhlo() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->afterEhlo($smtp) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> allowing($ext2)->afterEhlo($smtp) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->afterEhlo(any()) + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + } + + public function testExtensionsCanModifyMailFromParams() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $message = $this->_createMessage(); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> ignoring($message) + + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> one($buf)->write("MAIL FROM: FOO ZIP\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> inSequence($s) -> returns(3) + -> one($buf)->readLine(3) -> inSequence($s) -> returns("250 OK\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->getMailParams() -> returns('FOO') + -> allowing($ext1)->getPriorityOver('AUTH') -> returns(-1) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> one($ext2)->getMailParams() -> returns('ZIP') + -> allowing($ext2)->getPriorityOver('AUTH') -> returns(1) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->getMailParams() + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->send($message); + } + + public function testExtensionsCanModifyRcptParams() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $message = $this->_createMessage(); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> ignoring($message) + + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> one($buf)->write("MAIL FROM: \r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: FOO ZIP\r\n") -> inSequence($s) -> returns(3) + -> one($buf)->readLine(3) -> inSequence($s) -> returns("250 OK\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->getRcptParams() -> returns('FOO') + -> allowing($ext1)->getPriorityOver('AUTH') -> returns(-1) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> one($ext2)->getRcptParams() -> returns('ZIP') + -> allowing($ext2)->getPriorityOver('AUTH') -> returns(1) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->getRcptParams() + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->send($message); + } + + public function testExtensionsAreNotifiedOnCommand() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> one($buf)->write("FOO\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns("250 Cool\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->onCommand($smtp, "FOO\r\n", array(250, 251), optional()) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> one($ext2)->onCommand($smtp, "FOO\r\n", array(250, 251), optional()) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->onCommand(any(), any(), any(), optional()) + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->executeCommand("FOO\r\n", array(250, 251)); + } + + public function testChainOfCommandAlgorithmWhenNotifyingExtensions() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> never($buf)->write("FOO\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->onCommand($smtp, "FOO\r\n", array(250, 251), optional()) -> calls(array($this, 'cbStopCommand')) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> never($ext2)->onCommand(any(), any(), any(), optional()) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->onCommand(any(), any(), any(), optional()) + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->executeCommand("FOO\r\n", array(250, 251)); + } + + public function cbStopCommand(Yay_Invocation $invocation) + { + $args =& $invocation->getArguments(); + $stop =& $args[4]; + $stop = true; + return "250 ok"; + } + + public function testExtensionsCanExposeMixinMethods() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandlerMixin'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->exposeMixinMethods() -> returns(array('setUsername', 'setPassword')) + -> one($ext1)->setUsername('mick') + -> one($ext1)->setPassword('pass') + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $smtp->setUsername('mick'); + $smtp->setPassword('pass'); + } + + public function testMixinMethodsBeginningWithSetAndNullReturnAreFluid() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandlerMixin'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->exposeMixinMethods() -> returns(array('setUsername', 'setPassword')) + -> one($ext1)->setUsername('mick') -> returns(NULL) + -> one($ext1)->setPassword('pass') -> returns(NULL) + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $ret = $smtp->setUsername('mick'); + $this->assertReference($smtp, $ret); + $ret = $smtp->setPassword('pass'); + $this->assertReference($smtp, $ret); + } + + public function testMixinSetterWhichReturnValuesAreNotFluid() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandlerMixin'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->exposeMixinMethods() -> returns(array('setUsername', 'setPassword')) + -> one($ext1)->setUsername('mick') -> returns('x') + -> one($ext1)->setPassword('pass') -> returns('x') + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $this->assertEqual('x', $smtp->setUsername('mick')); + $this->assertEqual('x', $smtp->setPassword('pass')); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/EsmtpTransportTest.php b/lib/Swift/tests/unit/Swift/Transport/EsmtpTransportTest.php new file mode 100644 index 0000000..5862117 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/EsmtpTransportTest.php @@ -0,0 +1,242 @@ +_createEventDispatcher(); + } + return new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher); + } + + public function testHostCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setHost('foo'); + $this->assertEqual('foo', $smtp->getHost(), '%s: Host should be returned'); + } + + public function testPortCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setPort(25); + $this->assertEqual(25, $smtp->getPort(), '%s: Port should be returned'); + } + + public function testTimeoutCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setTimeout(10); + $this->assertEqual(10, $smtp->getTimeout(), '%s: Timeout should be returned'); + } + + public function testEncryptionCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setEncryption('tls'); + $this->assertEqual('tls', $smtp->getEncryption(), '%s: Crypto should be returned'); + } + + public function testStartSendsHeloToInitiate() + {//Overridden for EHLO instead + } + + public function testStartSendsEhloToInitiate() + { + /* -- RFC 2821, 3.2. + + 3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + + -- RFC 2281, 4.1.1.1. + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + -- RFC 2821, 4.3.2. + + EHLO or HELO + S: 250 + E: 504, 550 + + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .+?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + } + catch (Exception $e) + { + $this->fail('Starting Esmtp should send EHLO and accept 250 response'); + } + } + + public function testHeloIsUsedAsFallback() + { + /* -- RFC 2821, 4.1.4. + + If the EHLO command is not acceptable to the SMTP server, 501, 500, + or 502 failure replies MUST be returned as appropriate. The SMTP + server MUST stay in the same state after transmitting these replies + that it was in before the EHLO was received. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .+?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('501 WTF' . "\r\n") + -> one($buf)->write(pattern('~^HELO .+?\r\n$~D')) -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns('250 HELO' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $smtp->start(); + } + catch (Exception $e) + { + $this->fail( + 'Starting Esmtp should fallback to HELO if needed and accept 250 response' + ); + } + } + + public function testInvalidHeloResponseCausesException() + {//Overridden to first try EHLO + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('501 WTF' . "\r\n") + -> one($buf)->write(pattern('~^HELO .*?\r\n$~D')) -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns('504 WTF' . "\r\n") + ); + $this->_finishBuffer($buf); + try + { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('Non 250 HELO response should raise Exception'); + } + catch (Exception $e) + { + $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed'); + } + } + + public function testDomainNameIsPlacedInEhlo() + { + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write("EHLO mydomain.com\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testDomainNameIsPlacedInHelo() + { //Overridden to include ESMTP + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .+?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('501 WTF' . "\r\n") + -> one($buf)->write("HELO mydomain.com\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testFluidInterface() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + + $ref = $smtp + ->setHost('foo') + ->setPort(25) + ->setEncryption('tls') + ->setTimeout(30) + ; + $this->assertReference($ref, $smtp); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/FailoverTransportTest.php b/lib/Swift/tests/unit/Swift/Transport/FailoverTransportTest.php new file mode 100644 index 0000000..8f14773 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/FailoverTransportTest.php @@ -0,0 +1,345 @@ +mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message1, optional()) -> returns(1) -> when($con->is('on')) + -> one($t1)->send($message2, optional()) -> returns(1) -> when($con->is('on')) + -> ignoring($t1) + -> never($t2)->start() + -> never($t2)->send(any(), optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $context->assertIsSatisfied(); + } + + public function testMessageCanBeTriedOnNextTransportIfExceptionThrown() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testZeroIsReturnedIfTransportReturnsZero() + { + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message, optional()) -> returns(0) -> when($con->is('on')) + -> ignoring($t1) + ); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $this->assertEqual(0, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testTransportsWhichThrowExceptionsAreNotRetried() + { + $e = new Swift_TransportException('maur b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $message3 = $context->mock('Swift_Mime_Message'); + $message4 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> ignoring($message3) + -> ignoring($message4) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> never($t1)->send($message3, optional()) + -> never($t1)->send($message4, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message3, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message4, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $this->assertEqual(1, $transport->send($message3)); + $this->assertEqual(1, $transport->send($message4)); + } + + public function testExceptionIsThrownIfAllTransportsDie() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + try + { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } + catch (Exception $e) + { + } + $context->assertIsSatisfied(); + } + + public function testStoppingTransportStopsAllDelegates() + { + $context = new Mockery(); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('on'); + $con2 = $context->states('Connection')->startsAs('on'); + $context->checking(Expectations::create() + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->stop() -> when($con1->is('on')) -> then($con1->is('off')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->stop() -> when($con2->is('on')) -> then($con2->is('off')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $transport->stop(); + $context->assertIsSatisfied(); + } + + public function testTransportShowsAsNotStartedIfAllDelegatesDead() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try + { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } + catch (Exception $e) + { + $this->assertFalse($transport->isStarted()); + } + $context->assertIsSatisfied(); + } + + public function testRestartingTransportRestartsDeadDelegates() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> exactly(2)->of($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) -> then($con1->is('off')) + -> one($t1)->send($message2, optional()) -> returns(10) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> throws($e) -> when($con2->is('on')) + -> never($t2)->send($message2, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try + { + $transport->send($message1); + $this->fail('All transports failed so Exception should be thrown'); + } + catch (Exception $e) + { + $this->assertFalse($transport->isStarted()); + } + //Restart and re-try + $transport->start(); + $this->assertTrue($transport->isStarted()); + $this->assertEqual(10, $transport->send($message2)); + $context->assertIsSatisfied(); + } + + public function testFailureReferenceIsPassedToDelegates() + { + $failures = array(); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message, reference($failures)) -> returns(1) -> when($con->is('on')) + -> ignoring($t1) + ); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $transport->send($message, $failures); + $context->assertIsSatisfied(); + } + + public function testRegisterPluginDelegatesToLoadedTransports() + { + $context = new Mockery(); + + $plugin = $this->_createPlugin($context); + + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $context->checking(Expectations::create() + -> one($t1)->registerPlugin($plugin) + -> one($t2)->registerPlugin($plugin) + -> ignoring($t1) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->registerPlugin($plugin); + + $context->assertIsSatisfied(); + } + + // -- Private helpers + + private function _getTransport(array $transports) + { + $transport = new Swift_Transport_FailoverTransport(); + $transport->setTransports($transports); + return $transport; + } + + private function _createPlugin($context) + { + return $context->mock('Swift_Events_EventListener'); + } + + private function _createInnerTransport() + { + return $this->_mockery()->mock('Swift_Transport'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/LoadBalancedTransportTest.php b/lib/Swift/tests/unit/Swift/Transport/LoadBalancedTransportTest.php new file mode 100644 index 0000000..6622803 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/LoadBalancedTransportTest.php @@ -0,0 +1,428 @@ +mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection 1')->startsAs('off'); + $con2 = $context->states('Connection 2')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> allowing($t1)->start() -> when($con1->is('off')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> returns(1) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> allowing($t2)->start() -> when($con2->is('off')) -> then($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> never($t2)->send($message1, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + + $context->assertIsSatisfied(); + } + + public function testTransportsAreReusedInRotatingFashion() + { + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $message3 = $context->mock('Swift_Mime_Message'); + $message4 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection 1')->startsAs('off'); + $con2 = $context->states('Connection 2')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> allowing($t1)->start() -> when($con1->is('off')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> returns(1) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> one($t1)->send($message3, optional()) -> returns(1) -> when($con1->is('on')) + -> never($t1)->send($message4, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> allowing($t2)->start() -> when($con2->is('off')) -> then($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> never($t2)->send($message1, optional()) + -> one($t2)->send($message4, optional()) -> returns(1) -> when($con2->is('on')) + -> never($t2)->send($message3, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $this->assertEqual(1, $transport->send($message3)); + $this->assertEqual(1, $transport->send($message4)); + + $context->assertIsSatisfied(); + } + + public function testMessageCanBeTriedOnNextTransportIfExceptionThrown() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testMessageIsTriedOnNextTransportIfZeroReturned() + { + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> returns(0) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testZeroIsReturnedIfAllTransportsReturnZero() + { + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> returns(0) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(0) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(0, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testTransportsWhichThrowExceptionsAreNotRetried() + { + $e = new Swift_TransportException('maur b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $message3 = $context->mock('Swift_Mime_Message'); + $message4 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> ignoring($message3) + -> ignoring($message4) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> never($t1)->send($message3, optional()) + -> never($t1)->send($message4, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message3, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message4, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $this->assertEqual(1, $transport->send($message3)); + $this->assertEqual(1, $transport->send($message4)); + } + + public function testExceptionIsThrownIfAllTransportsDie() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + try + { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } + catch (Exception $e) + { + } + $context->assertIsSatisfied(); + } + + public function testStoppingTransportStopsAllDelegates() + { + $context = new Mockery(); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('on'); + $con2 = $context->states('Connection')->startsAs('on'); + $context->checking(Expectations::create() + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->stop() -> when($con1->is('on')) -> then($con1->is('off')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->stop() -> when($con2->is('on')) -> then($con2->is('off')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $transport->stop(); + $context->assertIsSatisfied(); + } + + public function testTransportShowsAsNotStartedIfAllDelegatesDead() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try + { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } + catch (Exception $e) + { + $this->assertFalse($transport->isStarted()); + } + $context->assertIsSatisfied(); + } + + public function testRestartingTransportRestartsDeadDelegates() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> exactly(2)->of($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) -> then($con1->is('off')) + -> one($t1)->send($message2, optional()) -> returns(10) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> throws($e) -> when($con2->is('on')) + -> never($t2)->send($message2, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try + { + $transport->send($message1); + $this->fail('All transports failed so Exception should be thrown'); + } + catch (Exception $e) + { + $this->assertFalse($transport->isStarted()); + } + //Restart and re-try + $transport->start(); + $this->assertTrue($transport->isStarted()); + $this->assertEqual(10, $transport->send($message2)); + $context->assertIsSatisfied(); + } + + public function testFailureReferenceIsPassedToDelegates() + { + $failures = array(); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message, reference($failures)) -> returns(1) -> when($con->is('on')) + -> ignoring($t1) + ); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $transport->send($message, $failures); + $context->assertIsSatisfied(); + } + + public function testRegisterPluginDelegatesToLoadedTransports() + { + $context = new Mockery(); + + $plugin = $this->_createPlugin($context); + + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $context->checking(Expectations::create() + -> one($t1)->registerPlugin($plugin) + -> one($t2)->registerPlugin($plugin) + -> ignoring($t1) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->registerPlugin($plugin); + + $context->assertIsSatisfied(); + } + + // -- Private helpers + + private function _getTransport(array $transports) + { + $transport = new Swift_Transport_LoadBalancedTransport(); + $transport->setTransports($transports); + return $transport; + } + + private function _createPlugin($context) + { + return $context->mock('Swift_Events_EventListener'); + } + + private function _createInnerTransport() + { + return $this->_mockery()->mock('Swift_Transport'); + } + +} \ No newline at end of file diff --git a/lib/Swift/tests/unit/Swift/Transport/MailTransportTest.php b/lib/Swift/tests/unit/Swift/Transport/MailTransportTest.php new file mode 100644 index 0000000..a3f4af6 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/MailTransportTest.php @@ -0,0 +1,319 @@ +_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $transport->send($message); + } + + public function testTransportUsesToFieldBodyInSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($to)->getFieldBody() -> returns("Foo ") + -> one($invoker)->mail("Foo ", any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($to) + ); + + $transport->send($message); + } + + public function testTransportUsesSubjectFieldBodyInSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subj = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subj + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($subj)->getFieldBody() -> returns("Thing") + -> one($invoker)->mail(any(), "Thing", any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($subj) + ); + + $transport->send($message); + } + + public function testTransportUsesBodyOfMessage() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->toString() -> returns( + "To: Foo \r\n" . + "\r\n" . + "This body" + ) + -> one($invoker)->mail(any(), any(), "This body", any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $transport->send($message); + } + + public function testTransportUsesHeadersFromMessage() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->toString() -> returns( + "Subject: Stuff\r\n" . + "\r\n" . + "This body" + ) + -> one($invoker)->mail(any(), any(), any(), "Subject: Stuff" . PHP_EOL, optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $transport->send($message); + } + + public function testTransportReturnsCountOfAllRecipientsIfInvokerReturnsTrue() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null, 'zip@button'=>null)) + -> allowing($message)->getCc() -> returns(array('test@test'=>null)) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) -> returns(true) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $this->assertEqual(3, $transport->send($message)); + } + + public function testTransportReturnsZeroIfInvokerReturnsFalse() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null, 'zip@button'=>null)) + -> allowing($message)->getCc() -> returns(array('test@test'=>null)) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) -> returns(false) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $this->assertEqual(0, $transport->send($message)); + } + + public function testToHeaderIsRemovedFromHeaderSetDuringSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->remove('To') + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($to) + ); + + $transport->send($message); + } + + public function testSubjectHeaderIsRemovedFromHeaderSetDuringSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subject = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subject + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->remove('Subject') + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($subject) + ); + + $transport->send($message); + } + + public function testToHeaderIsPutBackAfterSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->set($to, optional()) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($to) + ); + + $transport->send($message); + } + + public function testSubjectHeaderIsPutBackAfterSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subject = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subject + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->set($subject, optional()) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($subject) + ); + + $transport->send($message); + } + + // -- Creation Methods + + private function _createTransport($invoker, $dispatcher) + { + return new Swift_Transport_MailTransport($invoker, $dispatcher); + } + + private function _createEventDispatcher() + { + return $this->_mock('Swift_Events_EventDispatcher'); + } + + private function _createInvoker() + { + return $this->_mock('Swift_Transport_MailInvoker'); + } + + private function _createMessage($headers) + { + $message = $this->_mock('Swift_Mime_Message'); + + $this->_checking(Expectations::create() + -> allowing($message)->getHeaders() -> returns($headers) + ); + + return $message; + } + + private function _createHeaders($headers = array()) + { + $set = $this->_mock('Swift_Mime_HeaderSet'); + + if (count($headers) > 0) + { + foreach ($headers as $name => $header) + { + $this->_checking(Expectations::create() + -> allowing($set)->get($name) -> returns($header) + -> allowing($set)->has($name) -> returns(true) + ); + } + } + + $header = $this->_createHeader(); + $this->_checking(Expectations::create() + -> allowing($set)->get(any()) -> returns($header) + -> allowing($set)->has(any()) -> returns(true) + -> ignoring($header) + ); + + return $set; + } + + private function _createHeader() + { + return $this->_mock('Swift_Mime_Header'); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/SendmailTransportTest.php b/lib/Swift/tests/unit/Swift/Transport/SendmailTransportTest.php new file mode 100644 index 0000000..f910cfa --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/SendmailTransportTest.php @@ -0,0 +1,137 @@ +_createEventDispatcher(); + } + $transport = new Swift_Transport_SendmailTransport($buf, $dispatcher); + $transport->setCommand($command); + return $transport; + } + + protected function _getSendmail($buf, $dispatcher = null) + { + if (!$dispatcher) + { + $dispatcher = $this->_createEventDispatcher(); + } + $sendmail = new Swift_Transport_SendmailTransport($buf, $dispatcher); + return $sendmail; + } + + public function testCommandCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + + $sendmail->setCommand('/usr/sbin/sendmail -bs'); + $this->assertEqual('/usr/sbin/sendmail -bs', $sendmail->getCommand()); + $sendmail->setCommand('/usr/sbin/sendmail -oi -t'); + $this->assertEqual('/usr/sbin/sendmail -oi -t', $sendmail->getCommand()); + } + + public function testSendingMessageIn_t_ModeUsesSimplePipe() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->toByteStream($buf) + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n", "\n." => "\n..")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testSendingIn_t_ModeWith_i_FlagDoesntEscapeDot() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->toByteStream($buf) + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -i -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testSendingInTModeWith_oi_FlagDoesntEscapeDot() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->toByteStream($buf) + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -oi -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testSendingMessageRegeneratesId() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->generateId() + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n", "\n." => "\n..")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testFluidInterface() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getTransport($buf); + + $ref = $sendmail->setCommand('/foo'); + $this->assertReference($ref, $sendmail); + } + +} diff --git a/lib/Swift/tests/unit/Swift/Transport/StreamBufferTest.php b/lib/Swift/tests/unit/Swift/Transport/StreamBufferTest.php new file mode 100644 index 0000000..a191922 --- /dev/null +++ b/lib/Swift/tests/unit/Swift/Transport/StreamBufferTest.php @@ -0,0 +1,52 @@ +_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createFilter('a', 'b') -> returns($this->_createFilter()) + -> never($factory) + ); + $buffer = $this->_createBuffer($factory); + $buffer->setWriteTranslations(array('a' => 'b')); + } + + public function testOverridingTranslationsOnlyAddsNeededFilters() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createFilter('a', 'b') -> returns($this->_createFilter()) + -> one($factory)->createFilter('x', 'y') -> returns($this->_createFilter()) + -> never($factory) + ); + $buffer = $this->_createBuffer($factory); + $buffer->setWriteTranslations(array('a' => 'b')); + $buffer->setWriteTranslations(array('x' => 'y', 'a' => 'b')); + } + + // -- Creation methods + + private function _createBuffer($replacementFactory) + { + return new Swift_Transport_StreamBuffer($replacementFactory); + } + + private function _createFactory() + { + return $this->_mock('Swift_ReplacementFilterFactory'); + } + + private function _createFilter() + { + return $this->_stub('Swift_StreamFilter'); + } + +} diff --git a/lib/Twig-1.0.0-RC1/AUTHORS b/lib/Twig-1.0.0-RC1/AUTHORS new file mode 100755 index 0000000..eb5db05 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/AUTHORS @@ -0,0 +1,9 @@ +Twig is written and maintained by the Twig Team: + +Lead Developer: + +- Fabien Potencier + +Project Founder: + +- Armin Ronacher diff --git a/lib/Twig-1.0.0-RC1/CHANGELOG b/lib/Twig-1.0.0-RC1/CHANGELOG new file mode 100755 index 0000000..ee47434 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/CHANGELOG @@ -0,0 +1,277 @@ +* 1.0.0-RC1 (2011-01-09) + +Backward incompatibilities: + + * the "items" filter, which has been deprecated for quite a long time now, has been removed + * the "range" filter has been converted to a function: 0|range(10) -> range(0, 10) + * the "constant" filter has been converted to a function: {{ some_date|date('DATE_W3C'|constant) }} -> {{ some_date|date(constant('DATE_W3C')) }} + * the "cycle" filter has been converted to a function: {{ ['odd', 'even']|cycle(i) }} -> {{ cycle(['odd', 'even'], i) }} + * the "for" tag does not support "joined by" anymore + * the "autoescape" first argument is now "true"/"false" (instead of "on"/"off") + * the "parent" tag has been replaced by a "parent" function ({{ parent() }} instead of {% parent %}) + * the "display" tag has been replaced by a "block" function ({{ block('title') }} instead of {% display title %}) + * removed the grammar and simple token parser (moved to the Twig Extensions repository) + +Changes: + + * added "needs_context" option for filters and functions (the context is then passed as a first argument) + * added global variables support + * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode) + * added the "from" tag to import macros as functions + * added support for functions (a function is just syntactic sugar for a getAttribute() call) + * made macros callable when sandbox mode is enabled + * added an exception when a macro uses a reserved name + * the "default" filter now uses the "empty" test instead of just checking for null + * added the "empty" test + +* 0.9.10 (2010-12-16) + +Backward incompatibilities: + + * The Escaper extension is enabled by default, which means that all displayed + variables are now automatically escaped. You can revert to the previous + behavior by removing the extension via $env->removeExtension('escaper') + or just set the 'autoescape' option to 'false'. + * removed the "without loop" attribute for the "for" tag (not needed anymore + as the Optimizer take care of that for most cases) + * arrays and hashes have now a different syntax + * arrays keep the same syntax with square brackets: [1, 2] + * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"}) + * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1}) + * the i18n extension is now part of the Twig Extensions repository + +Changes: + + * added the merge filter + * removed 'is_escaper' option for filters (a left over from the previous version) -- you must use 'is_safe' now instead + * fixed usage of operators as method names (like is, in, and not) + * changed the order of execution for node visitors + * fixed default() filter behavior when used with strict_variables set to on + * fixed filesystem loader compatibility with PHAR files + * enhanced error messages when an unexpected token is parsed in an expression + * fixed filename not being added to syntax error messages + * added the autoescape option to enable/disable autoescaping + * removed the newline after a comment (mimicks PHP behavior) + * added a syntax error exception when parent block is used on a template that does not extend another one + * made the Escaper extension enabled by default + * fixed sandbox extension when used with auto output escaping + * fixed escaper when wrapping a Twig_Node_Print (the original class must be preserved) + * added an Optimizer extension (enabled by default; optimizes "for" loops and "raw" filters) + * added priority to node visitors + +* 0.9.9 (2010-11-28) + +Backward incompatibilities: + * the self special variable has been renamed to _self + * the odd and even filters are now tests: + {{ foo|odd }} must now be written {{ foo is odd }} + * the "safe" filter has been renamed to "raw" + * in Node classes, + sub-nodes are now accessed via getNode() (instead of property access) + attributes via getAttribute() (instead of array access) + * the urlencode filter had been renamed to url_encode + * the include tag now merges the passed variables with the current context by default + (the old behavior is still possible by adding the "only" keyword) + * moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime) + * removed support for {{ 1 < i < 3 }} (use {{ i > 1 and i < 3 }} instead) + * the "in" filter has been removed ({{ a|in(b) }} should now be written {{ a in b }}) + +Changes: + * added file and line to Twig_Error_Runtime exceptions thrown from Twig_Template + * changed trans tag to accept any variable for the plural count + * fixed sandbox mode (__toString() method check was not enforced if called implicitly from complex statements) + * added the ** (power) operator + * changed the algorithm used for parsing expressions + * added the spaceless tag + * removed trim_blocks option + * added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar()) + * changed all exceptions to extend Twig_Error + * fixed unary expressions ({{ not(1 or 0) }}) + * fixed child templates (with an extend tag) that uses one or more imports + * added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }}) + * escaping has been rewritten + * the implementation of template inheritance has been rewritten + (blocks can now be called individually and still work with inheritance) + * fixed error handling for if tag when a syntax error occurs within a subparse process + * added a way to implement custom logic for resolving token parsers given a tag name + * fixed js escaper to be stricter (now uses a whilelist-based js escaper) + * added the following filers: "constant", "trans", "replace", "json_encode" + * added a "constant" test + * fixed objects with __toString() not being autoescaped + * fixed subscript expressions when calling __call() (methods now keep the case) + * added "test" feature (accessible via the "is" operator) + * removed the debug tag (should be done in an extension) + * fixed trans tag when no vars are used in plural form + * fixed race condition when writing template cache + * added the special _charset variable to reference the current charset + * added the special _context variable to reference the current context + * renamed self to _self (to avoid conflict) + * fixed Twig_Template::getAttribute() for protected properties + +* 0.9.8 (2010-06-28) + +Backward incompatibilities: + * the trans tag plural count is now attached to the plural tag: + old: `{% trans count %}...{% plural %}...{% endtrans %}` + new: `{% trans %}...{% plural count %}...{% endtrans %}` + + * added a way to translate strings coming from a variable ({% trans var %}) + * fixed trans tag when used with the Escaper extension + * fixed default cache umask + * removed Twig_Template instances from the debug tag output + * fixed objects with __isset() defined + * fixed set tag when used with a capture + * fixed type hinting for Twig_Environment::addFilter() method + +* 0.9.7 (2010-06-12) + +Backward incompatibilities: + * changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %}) + * removed the sandboxed attribute of the include tag (use the new sandbox tag instead) + * refactored the Node system (if you have custom nodes, you will have to update them to use the new API) + + * added self as a special variable that refers to the current template (useful for importing macros from the current template) + * added Twig_Template instance support to the include tag + * added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %}) + * added a grammar sub-framework to ease the creation of custom tags + * fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface) + * removed the Twig_Resource::resolveMissingFilter() method + * fixed the filter tag which did not apply filtering to included files + * added a bunch of unit tests + * added a bunch of phpdoc + * added a sandbox tag in the sandbox extension + * changed the date filter to support any date format supported by DateTime + * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default) + * added the lexer, parser, and compiler as arguments to the Twig_Environment constructor + * changed the cache option to only accepts an explicit path to a cache directory or false + * added a way to add token parsers, filters, and visitors without creating an extension + * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface + * changed the generated code to match the new coding standards + * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }}) + * added an exception when a child template has a non-empty body (as it is always ignored when rendering) + +* 0.9.6 (2010-05-12) + + * fixed variables defined outside a loop and for which the value changes in a for loop + * fixed the test suite for PHP 5.2 and older versions of PHPUnit + * added support for __call() in expression resolution + * fixed node visiting for macros (macros are now visited by visitors as any other node) + * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now) + * added the cycle filter + * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII + * added a long-syntax for the set tag ({% set foo %}...{% endset %}) + * unit tests are now powered by PHPUnit + * added support for gettext via the `i18n` extension + * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values + * added a more useful exception if an if tag is not closed properly + * added support for escaping strategy in the autoescape tag + * fixed lexer when a template has a big chunk of text between/in a block + +* 0.9.5 (2010-01-20) + +As for any new release, don't forget to remove all cached templates after +upgrading. + +If you have defined custom filters, you MUST upgrade them for this release. To +upgrade, replace "array" with "new Twig_Filter_Function", and replace the +environment constant by the "needs_environment" option: + + // before + 'even' => array('twig_is_even_filter', false), + 'escape' => array('twig_escape_filter', true), + + // after + 'even' => new Twig_Filter_Function('twig_is_even_filter'), + 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)), + +If you have created NodeTransformer classes, you will need to upgrade them to +the new interface (please note that the interface is not yet considered +stable). + + * fixed list nodes that did not extend the Twig_NodeListInterface + * added the "without loop" option to the for tag (it disables the generation of the loop variable) + * refactored node transformers to node visitors + * fixed automatic-escaping for blocks + * added a way to specify variables to pass to an included template + * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules) + * improved the filter system to allow object methods to be used as filters + * changed the Array and String loaders to actually make use of the cache mechanism + * included the default filter function definitions in the extension class files directly (Core, Escaper) + * added the // operator (like the floor() PHP function) + * added the .. operator (as a syntactic sugar for the range filter when the step is 1) + * added the in operator (as a syntactic sugar for the in filter) + * added the following filters in the Core extension: in, range + * added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes) + * enhanced some error messages to provide better feedback in case of parsing errors + +* 0.9.4 (2009-12-02) + +If you have custom loaders, you MUST upgrade them for this release: The +Twig_Loader base class has been removed, and the Twig_LoaderInterface has also +been changed (see the source code for more information or the documentation). + + * added support for DateTime instances for the date filter + * fixed loop.last when the array only has one item + * made it possible to insert newlines in tag and variable blocks + * fixed a bug when a literal '\n' were present in a template text + * fixed bug when the filename of a template contains */ + * refactored loaders + +* 0.9.3 (2009-11-11) + +This release is NOT backward compatible with the previous releases. + + The loaders do not take the cache and autoReload arguments anymore. Instead, + the Twig_Environment class has two new options: cache and auto_reload. + Upgrading your code means changing this kind of code: + + $loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true); + $twig = new Twig_Environment($loader); + + to something like this: + + $loader = new Twig_Loader_Filesystem('/path/to/templates'); + $twig = new Twig_Environment($loader, array( + 'cache' => '/path/to/compilation_cache', + 'auto_reload' => true, + )); + + * deprecated the "items" filter as it is not needed anymore + * made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader + * optimized template loading speed + * removed output when an error occurs in a template and render() is used + * made major speed improvements for loops (up to 300% on even the smallest loops) + * added properties as part of the sandbox mode + * added public properties support (obj.item can now be the item property on the obj object) + * extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} ) + * fixed bug when \ was used in HTML + +* 0.9.2 (2009-10-29) + + * made some speed optimizations + * changed the cache extension to .php + * added a js escaping strategy + * added support for short block tag + * changed the filter tag to allow chained filters + * made lexer more flexible as you can now change the default delimiters + * added set tag + * changed default directory permission when cache dir does not exist (more secure) + * added macro support + * changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance + * made Twig_Autoloader::autoload() a static method + * avoid writing template file if an error occurs + * added $ escaping when outputting raw strings + * enhanced some error messages to ease debugging + * fixed empty cache files when the template contains an error + +* 0.9.1 (2009-10-14) + + * fixed a bug in PHP 5.2.6 + * fixed numbers with one than one decimal + * added support for method calls with arguments ({{ foo.bar('a', 43) }}) + * made small speed optimizations + * made minor tweaks to allow better extensibility and flexibility + +* 0.9.0 (2009-10-12) + + * Initial release diff --git a/lib/Twig-1.0.0-RC1/LICENSE b/lib/Twig-1.0.0-RC1/LICENSE new file mode 100755 index 0000000..5063d8d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2009 by the Twig Team, see AUTHORS for more details. + +Some 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. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +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. diff --git a/lib/Twig-1.0.0-RC1/README.markdown b/lib/Twig-1.0.0-RC1/README.markdown new file mode 100755 index 0000000..3c77450 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/README.markdown @@ -0,0 +1,8 @@ +Twig, the flexible, fast, and secure template language for PHP +============================================================== + +Twig is a template language for PHP, released under the new BSD license (code +and documentation). + +Twig uses a syntax similar to the Django and Jinja template languages which +inspired the Twig runtime environment. diff --git a/lib/Twig-1.0.0-RC1/bin/create_pear_package.php b/lib/Twig-1.0.0-RC1/bin/create_pear_package.php new file mode 100755 index 0000000..4899927 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/bin/create_pear_package.php @@ -0,0 +1,42 @@ + date('Y-m-d'), + 'time' => date('H:m:00'), + 'version' => $argv[1], + 'api_version' => $argv[1], + 'stability' => $argv[2], + 'api_stability' => $argv[2], +); + +$context['files'] = ''; +$path = realpath(dirname(__FILE__).'/../lib/Twig'); +foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY) as $file) +{ + if (preg_match('/\.php$/', $file)) + { + $name = str_replace($path.'/', '', $file); + $context['files'] .= ' '."\n"; + } +} + +$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; +} diff --git a/lib/Twig-1.0.0-RC1/doc/advanced.rst b/lib/Twig-1.0.0-RC1/doc/advanced.rst new file mode 100755 index 0000000..7ca26fc --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/advanced.rst @@ -0,0 +1,416 @@ +Extending Twig +============== + +Twig can be extended in many ways; you can add extra tags, filters, tests, +operators, global variables, and functions. You can even extend the parser +itself with node visitors. + +.. note:: + + This chapter describes how to extend Twig easily. If you want to reuse + your changes in different projects or if you want to share them with + others, you should then create an extension as described in the next + chapter. + +Before extending Twig, you must understand the differences between all the +different possible extension points and when to use them. + +First, remember that Twig has two main language constructs: + +* ``{{ }}``: used to print the result of an expression evaluation; + +* ``{% %}``: used to execute statements. + +To understand why Twig exposes so many extension points, let's see how to +implement a *Lorem ipsum* generator (it needs to know the number of words to +generate). + +You can use a ``lipsum`` *tag*: + +.. code-block:: jinja + + {% lipsum 40 %} + +That works, but using a tag for ``lipsum`` is not a good idea for at least +three main reasons: + +* ``lipsum`` is not a language construct; +* The tag outputs something; +* The tag is not flexible as you cannot use it in an expression: + + .. code-block:: jinja + + {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }} + +In fact, you rarely need to create tags; and that's good news because tags are +the most complex extension point of Twig. + +Now, let's use a ``lipsum`` *filter*: + +.. code-block:: jinja + + {{ 40|lipsum }} + +Again, it works, but it looks weird. A filter transforms the passed value to +something else but here we use the value to indicate the number of words to +generate. + +Next, let's use a ``lipsum`` *function*: + +.. code-block:: jinja + + {{ lipsum(40) }} + +Here we go. For this specific example, the creation of a function is the +extension point to use. And you can use it anywhere an expression is accepted: + +.. code-block:: jinja + + {{ 'some text' ~ ipsum(40) ~ 'some more text' }} + + {% set ipsum = ipsum(40) %} + +Last but not the least, you can also use a *global* object with a method able +to generate lorem ipsum text: + +.. code-block:: jinja + + {{ text.lipsum(40) }} + +As a rule of thumb, use functions for frequently used features and global +objects for everything else. + +Keep in mind the following when you want to extend Twig: + +========== ========================== ========== ========================= +What? Implementation difficulty? How often? When? +========== ========================== ========== ========================= +*macro* trivial frequent Content generation +*global* trivial frequent Helper object +*function* trivial frequent Content generation +*filter* trivial frequent Value transformation +*tag* complex rare DSL language construct +*test* trivial rare Boolean decision +*operator* trivial rare Values transformation +========== ========================== ========== ========================= + +Globals +------- + +A global variable is like any other template variable, except that it's +available in all templates and macros:: + + $twig = new Twig_Environment($loader); + $twig->addGlobal('text', new Text()); + +You can then use the ``user`` variable anywhere in a template: + +.. code-block:: jinja + + {{ text.lipsum(40) }} + +Filters +------- + +A filter is a regular PHP function or an object method that takes the left +side of the filter (before the pipe ``|``) as first argument and the extra +arguments passed to the filter (within parentheses ``()``) as extra arguments. + +Defining a filter is as easy as associating the filter name with a PHP +callable. For instance, let's say you have the following code in a template: + +.. code-block:: jinja + + {{ 'TWIG'|lower }} + +When compiling this template to PHP, Twig looks for the PHP callable +associated with the ``lower`` filter. The ``lower`` filter is a built-in Twig +filter, and it is simply mapped to the PHP ``strtolower()`` function. After +compilation, the generated PHP code is roughly equivalent to: + +.. code-block:: html+php + + + +As you can see, the ``'TWIG'`` string is passed as a first argument to the PHP +function. + +A filter can also take extra arguments like in the following example: + +.. code-block:: jinja + + {{ now|date('d/m/Y') }} + +In this case, the extra arguments are passed to the function after the main +argument, and the compiled code is equivalent to: + +.. code-block:: html+php + + + +Let's see how to create a new filter. + +In this section, we will create a ``rot13`` filter, which should return the +`rot13`_ transformation of a string. Here is an example of its usage and the +expected output: + +.. code-block:: jinja + + {{ "Twig"|rot13 }} + + {# should displays Gjvt #} + +Adding a filter is as simple as calling the ``addFilter()`` method on the +``Twig_Environment`` instance:: + + $twig = new Twig_Environment($loader); + $twig->addFilter('rot13', new Twig_Filter_Function('rot13')); + +The second argument of ``addFilter()`` is an instance of ``Twig_Filter``. +Here, we use ``Twig_Filter_Function`` as the filter is a PHP function. The +first argument passed to the ``Twig_Filter_Function`` constructor is the name +of the PHP function to call, here ``str_rot13``, a native PHP function. + +Let's say I now want to be able to add a prefix before the converted string: + +.. code-block:: jinja + + {{ "Twig"|rot13('prefix_') }} + + {# should displays prefix_Gjvt #} + +As the PHP ``str_rot13()`` function does not support this requirement, let's +create a new PHP function:: + + function project_compute_rot13($string, $prefix = '') + { + return $prefix.str_rot13($string); + } + +As you can see, the ``prefix`` argument of the filter is passed as an extra +argument to the ``project_compute_rot13()`` function. + +Adding this filter is as easy as before:: + + $twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13')); + +For better encapsulation, a filter can also be defined as a static method of a +class. The ``Twig_Filter_Function`` class can also be used to register such +static methods as filters:: + + $twig->addFilter('rot13', new Twig_Filter_Function('SomeClass::rot13Filter')); + +.. tip:: + + In an extension, you can also define a filter as a static method of the + extension class. + +Environment aware Filters +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Twig_Filter`` classes take options as their last argument. For instance, +if you want access to the current environment instance in your filter, set the +``needs_environment`` option to ``true``:: + + $filter = new Twig_Filter_Function('str_rot13', array('needs_environment' => true)); + +Twig will then pass the current environment as the first argument to the +filter call:: + + function twig_compute_rot13(Twig_Environment $env, $string) + { + // get the current charset for instance + $charset = $env->getCharset(); + + return str_rot13($string); + } + +Automatic Escaping +~~~~~~~~~~~~~~~~~~ + +If automatic escaping is enabled, the output of the filter may be escaped +before printing. If your filter acts as an escaper (or explicitly outputs html +or javascript code), you will want the raw output to be printed. In such a +case, set the ``is_safe`` option:: + + $filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html'))); + +Some filters may have to work on already escaped or safe values. In such a +case, set the ``pre_escape`` option:: + + $filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html'))); + +Functions +--------- + +A function is a regular PHP function or an object method that can be called from +templates. + +.. code-block:: jinja + + {{ constant("DATE_W3C") }} + +When compiling this template to PHP, Twig looks for the PHP callable +associated with the ``constant`` function. The ``constant`` function is a built-in Twig +function, and it is simply mapped to the PHP ``constant()`` function. After +compilation, the generated PHP code is roughly equivalent to: + +.. code-block:: html+php + + + +Adding a function is similar to adding a filter. This can be done by calling the +``addFunction()`` method on the ``Twig_Environment`` instance:: + + $twig = new Twig_Environment($loader); + $twig->addFunction('functionName', new Twig_Function_Function('someFunction')); + $twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod')); + +Functions also support ``needs_environment`` and ``is_safe`` parameters. + +Tags +---- + +One of the most exciting feature of a template engine like Twig is the +possibility to define new language constructs. This is also the most complex +feature as you need to understand how Twig's internals work. + +Let's create a simple ``set`` tag that allows the definition of simple +variables from within a template. The tag can be used like follows: + +.. code-block:: jinja + + {% set name = "value" %} + + {{ name }} + + {# should output value #} + +.. note:: + + The ``set`` tag is part of the Core extension and as such is always + available. The built-in version is slightly more powerful and supports + multiple assignments by default (cf. the template designers chapter for + more information). + +Three steps are needed to define a new tag: + +* Defining a Token Parser class (responsible for parsing the template code); + +* Defining a Node class (responsible for converting the parsed code to PHP); + +* Registering the tag. + +Registering a new tag +~~~~~~~~~~~~~~~~~~~~~ + +Adding a tag is as simple as calling the ``addTokenParser`` method on the +``Twig_Environment`` instance:: + + $twig = new Twig_Environment($loader); + $twig->addTokenParser(new Project_Set_TokenParser()); + +Defining a Token Parser +~~~~~~~~~~~~~~~~~~~~~~~ + +Now, let's see the actual code of this class:: + + class Project_Set_TokenParser extends Twig_TokenParser + { + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(); + $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '='); + $value = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Project_Set_Node($name, $value, $lineno, $this->getTag()); + } + + public function getTag() + { + return 'set'; + } + } + +The ``getTag()`` method must return the tag we want to parse, here ``set``. + +The ``parse()`` method is invoked whenever the parser encounters a ``set`` +tag. It should return a ``Twig_Node`` instance that represents the node (the +``Project_Set_Node`` calls creating is explained in the next section). + +The parsing process is simplified thanks to a bunch of methods you can call +from the token stream (``$this->parser->getStream()``): + +* ``getCurrent()``: Gets the current token in the stream. + +* ``next()``: Moves to the next token in the stream, *but returns the old one*. + +* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether + the current token is of a particular type or value (or both). The value may be an + array of several possible values. + +* ``expect($type[, $value[, $message]])``: If the current token isn't of the given + type/value a syntax error is thrown. Otherwise, if the type and value are correct, + the token is returned and the stream moves to the next token. + +* ``look()``: Looks a the next token without consuming it. + +Parsing expressions is done by calling the ``parseExpression()`` like we did for +the ``set`` tag. + +.. tip:: + + Reading the existing ``TokenParser`` classes is the best way to learn all + the nitty-gritty details of the parsing process. + +Defining a Node +~~~~~~~~~~~~~~~ + +The ``Project_Set_Node`` class itself is rather simple:: + + class Project_Set_Node extends Twig_Node + { + public function __construct($name, Twig_Node_Expression $value, $lineno) + { + parent::__construct(array('value' => $value), array('name' => $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('$context[\''.$this->getAttribute('name').'\'] = ') + ->subcompile($this->getNode('value')) + ->raw(";\n") + ; + } + } + +The compiler implements a fluid interface and provides methods that helps the +developer generate beautiful and readable PHP code: + +* ``subcompile()``: Compiles a node. + +* ``raw()``: Writes the given string as is. + +* ``write()``: Writes the given string by adding indentation at the beginning + of each line. + +* ``string()``: Writes a quoted string. + +* ``repr()``: Writes a PHP representation of a given value (see + ``Twig_Node_For`` for a usage example). + +* ``addDebugInfo()``: Adds the line of the original template file related to + the current node as a comment. + +* ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a + usage example). + +* ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a + usage example). + +.. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php diff --git a/lib/Twig-1.0.0-RC1/doc/api.rst b/lib/Twig-1.0.0-RC1/doc/api.rst new file mode 100755 index 0000000..2dcfb5a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/api.rst @@ -0,0 +1,474 @@ +Twig for Developers +=================== + +This chapter describes the API to Twig and not the template language. It will +be most useful as reference to those implementing the template interface to +the application and not those who are creating Twig templates. + +Basics +------ + +Twig uses a central object called the **environment** (of class +``Twig_Environment``). Instances of this class are used to store the +configuration and extensions, and are used to load templates from the file +system or other locations. + +Most applications will create one ``Twig_Environment`` object on application +initialization and use that to load templates. In some cases it's however +useful to have multiple environments side by side, if different configurations +are in use. + +The simplest way to configure Twig to load templates for your application +looks roughly like this:: + + require_once '/path/to/lib/Twig/Autoloader.php'; + Twig_Autoloader::register(); + + $loader = new Twig_Loader_Filesystem('/path/to/templates'); + $twig = new Twig_Environment($loader, array( + 'cache' => '/path/to/compilation_cache', + )); + +This will create a template environment with the default settings and a loader +that looks up the templates in the ``/path/to/templates/`` folder. Different +loaders are available and you can also write your own if you want to load +templates from a database or other resources. + +.. note:: + + Notice that the second argument of the environment is an array of options. + The ``cache`` option is a compilation cache directory, where Twig caches + the compiled templates to avoid the parsing phase for sub-sequent + requests. It is very different from the cache you might want to add for + the evaluated templates. For such a need, you can use any available PHP + cache library. + +To load a template from this environment you just have to call the +``loadTemplate()`` method which then returns a ``Twig_Template`` instance:: + + $template = $twig->loadTemplate('index.html'); + +To render the template with some variables, call the ``render()`` method:: + + echo $template->render(array('the' => 'variables', 'go' => 'here')); + +.. note:: + + The ``display()`` method is a shortcut to output the template directly. + +Environment Options +------------------- + +When creating a new ``Twig_Environment`` instance, you can pass an array of +options as the constructor second argument:: + + $twig = new Twig_Environment($loader, array('debug' => true)); + +The following options are available: + +* ``debug``: When set to ``true``, the generated templates have a + ``__toString()`` method that you can use to display the generated nodes + (default to ``false``). + +* ``charset``: The charset used by the templates (default to ``utf-8``). + +* ``base_template_class``: The base template class to use for generated + templates (default to ``Twig_Template``). + +* ``cache``: An absolute path where to store the compiled templates, or + ``false`` to disable caching (which is the default). + +* ``auto_reload``: When developing with Twig, it's useful to recompile the + template whenever the source code changes. If you don't provide a value for + the ``auto_reload`` option, it will be determined automatically based on the + ``debug`` value. + +* ``strict_variables``: If set to ``false``, Twig will silently ignore invalid + variables (variables and or attributes/methods that do not exist) and + replace them with a ``null`` value. When set to ``true``, Twig throws an + exception instead (default to ``false``). + +* ``autoescape``: If set to ``true``, auto-escaping will be enabled by default + for all templates (default to ``true``). + +* ``optimizations``: A flag that indicates which optimizations to apply + (default to ``-1`` -- all optimizations are enabled; set it to ``0`` to + disable). + +Loaders +------- + +Loaders are responsible for loading templates from a resource such as the file +system. + +Compilation Cache +~~~~~~~~~~~~~~~~~ + +All template loaders can cache the compiled templates on the filesystem for +future reuse. It speeds up Twig a lot as templates are only compiled once; and +the performance boost is even larger if you use a PHP accelerator such as APC. +See the ``cache`` and ``auto_reload`` options of ``Twig_Environment`` above +for more information. + +Built-in Loaders +~~~~~~~~~~~~~~~~ + +Here is a list of the built-in loaders Twig provides: + +* ``Twig_Loader_Filesystem``: Loads templates from the file system. This + loader can find templates in folders on the file system and is the preferred + way to load them:: + + $loader = new Twig_Loader_Filesystem($templateDir); + + It can also look for templates in an array of directories:: + + $loader = new Twig_Loader_Filesystem(array($templateDir1, $templateDir2)); + + With such a configuration, Twig will first look for templates in + ``$templateDir1`` and if they do not exist, it will fallback to look for + them in the ``$templateDir2``. + +* ``Twig_Loader_String``: Loads templates from a string. It's a dummy loader + as you pass it the source code directly:: + + $loader = new Twig_Loader_String(); + +* ``Twig_Loader_Array``: Loads a template from a PHP array. It's passed an + array of strings bound to template names. This loader is useful for unit + testing:: + + $loader = new Twig_Loader_Array($templates); + +.. tip:: + + When using the ``Array`` or ``String`` loaders with a cache mechanism, you + should know that a new cache key is generated each time a template content + "changes" (the cache key being the source code of the template). If you + don't want to see your cache grows out of control, you need to take care + of clearing the old cache file by yourself. + +Create your own Loader +~~~~~~~~~~~~~~~~~~~~~~ + +All loaders implement the ``Twig_LoaderInterface``:: + + interface Twig_LoaderInterface + { + /** + * Gets the source code of a template, given its name. + * + * @param string $name string The name of the template to load + * + * @return string The template source code + */ + function getSource($name); + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name string The name of the template to load + * + * @return string The cache key + */ + function getCacheKey($name); + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + function isFresh($name, $time); + } + +As an example, here is how the built-in ``Twig_Loader_String`` reads:: + + class Twig_Loader_String implements Twig_LoaderInterface + { + public function getSource($name) + { + return $name; + } + + public function getCacheKey($name) + { + return $name; + } + + public function isFresh($name, $time) + { + return false; + } + } + +The ``isFresh()`` method must return ``true`` if the current cached template +is still fresh, given the last modification time, or ``false`` otherwise. + +Using Extensions +---------------- + +Twig extensions are packages that add new features to Twig. Using an +extension is as simple as using the ``addExtension()`` method:: + + $twig->addExtension(new Twig_Extension_Sandbox()); + +Twig comes bundled with the following extensions: + +* *Twig_Extension_Core*: Defines all the core features of Twig. + +* *Twig_Extension_Escaper*: Adds automatic output-escaping and the possibility + to escape/unescape blocks of code. + +* *Twig_Extension_Sandbox*: Adds a sandbox mode to the default Twig + environment, making it safe to evaluated untrusted code. + +* *Twig_Extension_Optimizer*: Optimizers the node tree before compilation. + +The core, escaper, and optimizer extensions do not need to be added to the +Twig environment, as they are registered by default. You can disable an +already registered extension:: + + $twig->removeExtension('escaper'); + +Built-in Extensions +------------------- + +This section describes the features added by the built-in extensions. + +.. tip:: + + Read the chapter about extending Twig to learn how to create your own + extensions. + +Core Extension +~~~~~~~~~~~~~~ + +The ``core`` extension defines all the core features of Twig: + +* Tags: + + * ``for`` + * ``if`` + * ``extends`` + * ``include`` + * ``block`` + * ``filter`` + * ``macro`` + * ``import`` + * ``from`` + * ``set`` + * ``spaceless`` + +* Filters: + + * ``date`` + * ``format`` + * ``replace`` + * ``url_encode`` + * ``json_encode`` + * ``title`` + * ``capitalize`` + * ``upper`` + * ``lower`` + * ``striptags`` + * ``join`` + * ``reverse`` + * ``length`` + * ``sort`` + * ``merge`` + * ``default`` + * ``keys`` + * ``escape`` + * ``e`` + +* Functions: + + * ``range`` + * ``constant`` + * ``cycle`` + * ``parent`` + * ``block`` + +* Tests: + + * ``even`` + * ``odd`` + * ``defined`` + * ``sameas`` + * ``none`` + * ``divisibleby`` + * ``constant`` + * ``empty`` + +Escaper Extension +~~~~~~~~~~~~~~~~~ + +The ``escaper`` extension adds automatic output escaping to Twig. It defines a +new tag, ``autoescape``, and a new filter, ``raw``. + +When creating the escaper extension, you can switch on or off the global +output escaping strategy:: + + $escaper = new Twig_Extension_Escaper(true); + $twig->addExtension($escaper); + +If set to ``true``, all variables in templates are escaped, except those using +the ``raw`` filter: + +.. code-block:: jinja + + {{ article.to_html|raw }} + +You can also change the escaping mode locally by using the ``autoescape`` tag: + +.. code-block:: jinja + + {% autoescape true %} + {% var %} + {% var|raw %} {# var won't be escaped #} + {% var|escape %} {# var won't be doubled-escaped #} + {% endautoescape %} + +.. warning:: + + The ``autoescape`` tag has no effect on included files. + +The escaping rules are implemented as follows: + +* Literals (integers, booleans, arrays, ...) used in the template directly as + variables or filter arguments are never automatically escaped: + + .. code-block:: jinja + + {{ "Twig
" }} {# won't be escaped #} + + {% set text = "Twig
" %} + {{ text }} {# will be escaped #} + +* Expressions which the result is always a literal or a variable marked safe + are never automatically escaped: + + .. code-block:: jinja + + {{ foo ? "Twig
" : "
Twig" }} {# won't be escaped #} + + {% set text = "Twig
" %} + {{ foo ? text : "
Twig" }} {# will be escaped #} + + {% set text = "Twig
" %} + {{ foo ? text|raw : "
Twig" }} {# won't be escaped #} + + {% set text = "Twig
" %} + {{ foo ? text|escape : "
Twig" }} {# the result of the expression won't be escaped #} + +* Escaping is applied before printing, after any other filter is applied: + + .. code-block:: jinja + + {{ var|upper }} {# is equivalent to {{ var|upper|escape }} #} + +* The `raw` filter should only be used at the end of the filter chain: + + .. code-block:: jinja + + {{ var|raw|upper }} {# will be escaped #} + + {{ var|upper|raw }} {# won't be escaped #} + +* Automatic escaping is not applied if the last filter in the chain is marked + safe for the current context (e.g. ``html`` or ``js``). ``escaper`` and + ``escaper('html')`` are marked safe for html, ``escaper('js')`` is marked + safe for javascript, ``raw`` is marked safe for everything. + + .. code-block:: jinja + + {% autoescape true js %} + {{ var|escape('html') }} {# will be escaped for html and javascript #} + {{ var }} {# will be escaped for javascript #} + {{ var|escape('js') }} {# won't be double-escaped #} + {% endautoescape %} + +.. note:: + + Note that autoescaping has some limitations as escaping is applied on + expressions after evaluation. For instance, when working with + concatenation, ``{{ foo|raw ~ bar }}`` won't give the expected result as + escaping is applied on the result of the concatenation, not on the + individual variables (so, the ``raw`` filter won't have any effect here). + +Sandbox Extension +~~~~~~~~~~~~~~~~~ + +The ``sandbox`` extension can be used to evaluate untrusted code. Access to +unsafe attributes and methods is prohibited. The sandbox security is managed +by a policy instance. By default, Twig comes with one policy class: +``Twig_Sandbox_SecurityPolicy``. This class allows you to white-list some +tags, filters, properties, and methods:: + + $tags = array('if'); + $filters = array('upper'); + $methods = array( + 'Article' => array('getTitle', 'getBody'), + ); + $properties = array( + 'Article' => array('title', 'body'), + ); + $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties); + +With the previous configuration, the security policy will only allow usage of +the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be +able to call the ``getTitle()`` and ``getBody()`` methods on ``Article`` +objects, and the ``title`` and ``body`` public properties. Everything else +won't be allowed and will generate a ``Twig_Sandbox_SecurityError`` exception. + +The policy object is the first argument of the sandbox constructor:: + + $sandbox = new Twig_Extension_Sandbox($policy); + $twig->addExtension($sandbox); + +By default, the sandbox mode is disabled and should be enabled when including +untrusted template code by using the ``sandbox`` tag: + +.. code-block:: jinja + + {% sandbox %} + {% include 'user.html' %} + {% endsandbox %} + +You can sandbox all templates by passing ``true`` as the second argument of +the extension constructor:: + + $sandbox = new Twig_Extension_Sandbox($policy, true); + +Optimizer Extension +~~~~~~~~~~~~~~~~~~~ + +The ``optimizer`` extension optimizes the node tree before compilation:: + + $twig->addExtension(new Twig_Extension_Optimizer()); + +By default, all optimizations are turned on. You can select the ones you want +to enable by passing them to the constructor:: + + $optimizer = new Twig_Extension_Optimizer(Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR); + + $twig->addExtension($optimizer); + +Exceptions +---------- + +Twig can throw exceptions: + +* ``Twig_Error``: The base exception for all errors. + +* ``Twig_Error_Syntax``: Thrown to tell the user that there is a problem with + the template syntax. + +* ``Twig_Error_Runtime``: Thrown when an error occurs at runtime (when a filter + does not exist for instance). + +* ``Twig_Error_Loader``: Thrown when an error occurs during template loading. + +* ``Twig_Sandbox_SecurityError``: Thrown when an unallowed tag, filter, or + method is called in a sandboxed template. diff --git a/lib/Twig-1.0.0-RC1/doc/extensions.rst b/lib/Twig-1.0.0-RC1/doc/extensions.rst new file mode 100755 index 0000000..1c4079b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/extensions.rst @@ -0,0 +1,302 @@ +Creating a Twig Extension +========================= + +The main motivation for writing an extension is to move often used code into a +reusable class like adding support for internationalization. An extension can +define tags, filters, tests, operators, global variables, functions, and node +visitors. + +Most of the time, it is useful to create a single extension for your project, +to host all the specific tags and filters you want to add to Twig. + +.. note:: + + Before writing your own extensions, have a look at the Twig official + extension repository: http://github.com/fabpot/Twig-extensions. + +An extension is a class that implements the following interface:: + + interface Twig_ExtensionInterface + { + /** + * Initializes the runtime environment. + * + * This is where you can load some file that contains filter functions for instance. + * + * @param Twig_Environment $environement The current Twig_Environment instance + */ + public function initRuntime(Twig_Environment $environement); + + /** + * Returns the token parser instances to add to the existing list. + * + * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances + */ + public function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return array An array of Twig_NodeVisitorInterface instances + */ + public function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return array An array of filters + */ + public function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return array An array of tests + */ + public function getTests(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array An array of operators + */ + public function getOperators(); + + /** + * Returns a list of global functions to add to the existing list. + * + * @return array An array of global functions + */ + public function getGlobals(); + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName(); + } + +To keep your extension class clean and lean, it can inherit from the built-in +``Twig_Extension`` class instead of implementing the whole interface. That +way, you just need to implement the ``getName()`` method as the +``Twig_Extension`` provides empty implementations for all other methods. + +The ``getName()`` method must return a unique identifier for your extension. + +Now, with this information in mind, let's create the most basic extension +possible:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getName() + { + return 'project'; + } + } + +.. note:: + + Of course, this extension does nothing for now. We will customize it in + the next sections. + +Twig does not care where you save your extension on the filesystem, as all +extensions must be registered explicitly to be available in your templates. + +You can register an extension by using the ``addExtension()`` method on your +main ``Environment`` object:: + + $twig = new Twig_Environment($loader); + $twig->addExtension(new Project_Twig_Extension()); + +Of course, you need to first load the extension file by either using +``require_once()`` or by using an autoloader (see `spl_autoload_register()`_). + +.. tip:: + + The bundled extensions are great examples of how extensions work. + +Globals and Functions +--------------------- + +Global variables and functions can be registered in an extensions via the +``getGlobals()`` method:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getGlobals() + { + return array( + 'text' => new Text(), + 'lipsum' => new Twig_Function(new Text(), 'getLipsum'), + ); + } + + // ... + } + +Filters +------- + +To add a filter to an extension, you need to override the ``getFilters()`` +method. This method must return an array of filters to add to the Twig +environment:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getFilters() + { + return array( + 'rot13' => new Twig_Filter_Function('str_rot13'), + ); + } + + // ... + } + +As you can see in the above code, the ``getFilters()`` method returns an array +where keys are the name of the filters (``rot13``) and the values the +definition of the filter (``new Twig_Filter_Function('str_rot13')``). + +As seen in the previous chapter, you can also define filters as static methods +on the extension class:: + +$twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter')); + +You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function`` +when defining a filter to use a method:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getFilters() + { + return array( + 'rot13' => new Twig_Filter_Method($this, 'rot13Filter'), + ); + } + + public function rot13Filter($string) + { + return str_rot13($string); + } + + // ... + } + +The first argument of the ``Twig_Filter_Method`` constructor is always +``$this``, the current extension object. The second one is the name of the +method to call. + +Using methods for filters is a great way to package your filter without +polluting the global namespace. This also gives the developer more flexibility +at the cost of a small overhead. + +Overriding default Filters +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If some default core filters do not suit your needs, you can easily override +them by creating your own core extension. Of course, you don't need to copy +and paste the whole core extension code of Twig. Instead, you can just extends +it and override the filter(s) you want by overriding the ``getFilters()`` +method:: + + class MyCoreExtension extends Twig_Extension_Core + { + public function getFilters() + { + return array_merge( + parent::getFilters(), + array( + 'date' => Twig_Filter_Method($this, 'dateFilter') + ) + ); + } + + public function dateFilter($timestamp, $format = 'F j, Y H:i') + { + return '...'.twig_date_format_filter($timestamp, $format); + } + + // ... + } + +Here, we override the ``date`` filter with a custom one. Using this new core +extension is as simple as registering the ``MyCoreExtension`` extension by +calling the ``addExtension()`` method on the environment instance:: + + $twig = new Twig_Environment($loader); + $twig->addExtension(new MyCoreExtension()); + +But I can already hear some people wondering how it can work as the Core +extension is loaded by default. That's true, but the trick is that both +extensions share the same unique identifier (``core`` - defined in the +``getName()`` method). By registering an extension with the same name as an +existing one, you have actually overridden the default one, even if it is +already registered:: + + $twig->addExtension(new Twig_Extension_Core()); + $twig->addExtension(new MyCoreExtension()); + +Tags +---- + +Adding a tag in an extension can be done by overriding the +``getTokenParsers()`` method. This method must return an array of tags to add +to the Twig environment:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getTokenParsers() + { + return array(new Project_Set_TokenParser()); + } + + // ... + } + +In the above code, we have added a single new tag, defined by the +``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is +responsible for parsing the tag and compiling it to PHP. + +Operators +--------- + +The ``getOperators()`` methods allows to add new operators. Here is how to add +``!``, ``||``, and ``&&`` operators:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getOperators() + { + return array( + array( + '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + ), + array( + '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + ), + ); + } + + // ... + } + +Tests +----- + +The ``getTests()`` methods allows to add new test functions:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getTests() + { + return array( + 'even' => new Twig_Test_Function('twig_test_even'), + ); + } + + // ... + } + +.. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register diff --git a/lib/Twig-1.0.0-RC1/doc/hacking.rst b/lib/Twig-1.0.0-RC1/doc/hacking.rst new file mode 100755 index 0000000..0a82b6c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/hacking.rst @@ -0,0 +1,184 @@ +Hacking Twig +============ + +Twig is very extensible and you can easily hack it. Keep in mind that you +should probably try to create an extension before hacking the core, as most +features and enhancements can be done with extensions. This chapter is also +useful for people who want to understand how Twig works under the hood. + +How Twig works? +--------------- + +The rendering of a Twig template can be summarized into four key steps: + +* **Load** the template: If the template is already compiled, load it and go + to the *evaluation* step, otherwise: + + * First, the **lexer** tokenizes the template source code into small pieces + for easier processing; + * Then, the **parser** converts the token stream into a meaningful tree + of nodes (the Abstract Syntax Tree); + * Eventually, the *compiler* transforms the AST into PHP code; + +* **Evaluate** the template: It basically means calling the ``display()`` + method of the compiled template and passing it the context. + +The Lexer +--------- + +The Twig lexer goal is to tokenize a source code into a token stream (each +token is of class ``Token``, and the stream is an instance of +``Twig_TokenStream``). The default lexer recognizes nine different token types: + +* ``Twig_Token::TEXT_TYPE`` +* ``Twig_Token::BLOCK_START_TYPE`` +* ``Twig_Token::VAR_START_TYPE`` +* ``Twig_Token::BLOCK_END_TYPE`` +* ``Twig_Token::VAR_END_TYPE`` +* ``Twig_Token::NAME_TYPE`` +* ``Twig_Token::NUMBER_TYPE`` +* ``Twig_Token::STRING_TYPE`` +* ``Twig_Token::OPERATOR_TYPE`` +* ``Twig_Token::EOF_TYPE`` + +You can manually convert a source code into a token stream by calling the +``tokenize()`` of an environment:: + + $stream = $twig->tokenize($source, $identifier); + +As the stream has a ``__toString()`` method, you can have a textual +representation of it by echoing the object:: + + echo $stream."\n"; + +Here is the output for the ``Hello {{ name }}`` template: + +.. code-block:: text + + TEXT_TYPE(Hello ) + VAR_START_TYPE() + NAME_TYPE(name) + VAR_END_TYPE() + EOF_TYPE() + +You can change the default lexer use by Twig (``Twig_Lexer``) by calling the +``setLexer()`` method:: + + $twig->setLexer($lexer); + +Lexer classes must implement the ``Twig_LexerInterface``:: + + interface Twig_LexerInterface + { + /** + * Tokenizes a source code. + * + * @param string $code The source code + * @param string $filename A unique identifier for the source code + * + * @return Twig_TokenStream A token stream instance + */ + function tokenize($code, $filename = 'n/a'); + } + +The Parser +---------- + +The parser converts the token stream into an AST (Abstract Syntax Tree), or a +node tree (of class ``Twig_Node_Module``). The core extension defines the +basic nodes like: ``for``, ``if``, ... and the expression nodes. + +You can manually convert a token stream into a node tree by calling the +``parse()`` method of an environment:: + + $nodes = $twig->parse($stream); + +Echoing the node object gives you a nice representation of the tree:: + + echo $nodes."\n"; + +Here is the output for the ``Hello {{ name }}`` template: + +.. code-block:: text + + Twig_Node_Module( + Twig_Node_Text(Hello ) + Twig_Node_Print( + Twig_Node_Expression_Name(name) + ) + ) + +The default parser (``Twig_TokenParser``) can be also changed by calling the +``setParser()`` method:: + + $twig->setParser($parser); + +All Twig parsers must implement the ``Twig_ParserInterface``:: + + interface Twig_ParserInterface + { + /** + * Converts a token stream to a node tree. + * + * @param Twig_TokenStream $stream A token stream instance + * + * @return Twig_Node_Module A node tree + */ + function parser(Twig_TokenStream $code); + } + +The Compiler +------------ + +The last step is done by the compiler. It takes a node tree as an input and +generates PHP code usable for runtime execution of the templates. The default +compiler generates PHP classes to ease the implementation of the template +inheritance feature. + +You can call the compiler by hand with the ``compile()`` method of an +environment:: + + $php = $twig->compile($nodes); + +The ``compile()`` method returns the PHP source code representing the node. + +The generated template for a ``Hello {{ name }}`` template reads as follows:: + + /* Hello {{ name }} */ + class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template + { + public function display($context) + { + $this->env->initRuntime(); + + // line 1 + echo "Hello "; + echo (isset($context['name']) ? $context['name'] : null); + } + } + +As for the lexer and the parser, the default compiler (``Twig_Compiler``) can +be changed by calling the ``setCompiler()`` method:: + + $twig->setCompiler($compiler); + +All Twig compilers must implement the ``Twig_CompilerInterface``:: + + interface Twig_CompilerInterface + { + /** + * Compiles a node. + * + * @param Twig_Node $node The node to compile + * + * @return Twig_Compiler The current compiler instance + */ + function compile(Twig_Node $node); + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + function getSource(); + } diff --git a/lib/Twig-1.0.0-RC1/doc/index.rst b/lib/Twig-1.0.0-RC1/doc/index.rst new file mode 100755 index 0000000..73edce0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/index.rst @@ -0,0 +1,13 @@ +Twig +==== + +.. toctree:: + :maxdepth: 2 + + intro + templates + api + advanced + extensions + hacking + recipes diff --git a/lib/Twig-1.0.0-RC1/doc/intro.rst b/lib/Twig-1.0.0-RC1/doc/intro.rst new file mode 100755 index 0000000..56116dc --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/intro.rst @@ -0,0 +1,99 @@ +Introduction +============ + +This is the documentation for Twig, the flexible, fast, and secure template +engine for PHP. + +If you have any exposure to other text-based template languages, such as +Smarty, Django, or Jinja, you should feel right at home with Twig. It's both +designer and developer friendly by sticking to PHP's principles and adding +functionality useful for templating environments. + +The key-features are... + +* *Fast*: Twig compiles templates down to plain optimized PHP code. The + overhead compared to regular PHP code was reduced to the very minimum. + +* *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This + allows Twig to be used as a template language for applications where users + may modify the template design. + +* *Flexible*: Twig is powered by a flexible lexer and parser. This allows the + developer to define its own custom tags and filters, and create its own DSL. + +Prerequisites +------------- + +Twig needs at least **PHP 5.2.4** to run. + +Installation +------------ + +You have multiple ways to install Twig. If you are unsure what to do, go with +the tarball. + +From the tarball release +~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Download the most recent tarball from the `download page`_ +2. Unpack the tarball +3. Move the files somewhere in your project + +Installing the development version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Install Subversion or Git +2. For Subversion: ``svn co http://svn.twig-project.org/trunk/ twig``, for Git: + ``git clone git://github.com/fabpot/Twig.git`` + +Installing the PEAR package +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Install PEAR +2. ``pear channel-discover pear.twig-project.org`` +3. ``pear install twig/Twig`` (or ``pear install twig/Twig-beta``) + +Basic API Usage +--------------- + +This section gives you a brief introduction to the PHP API for Twig. + +The first step to use Twig is to register its autoloader:: + + require_once '/path/to/lib/Twig/Autoloader.php'; + Twig_Autoloader::register(); + +Replace the ``/path/to/lib/`` path with the path you used for Twig +installation. + +.. note:: + + Twig follows the PEAR convention names for its classes, which means you + can easily integrate Twig classes loading in your own autoloader. + +.. code-block:: php + + $loader = new Twig_Loader_String(); + $twig = new Twig_Environment($loader); + + $template = $twig->loadTemplate('Hello {{ name }}!'); + + $template->display(array('name' => 'Fabien')); + +Twig uses a loader (``Twig_Loader_String``) to locate templates, and an +environment (``Twig_Environment``) to store the configuration. + +The ``loadTemplate()`` method uses the loader to locate and load the template +and returns a template object (``Twig_Template``) which is suitable for +rendering with the ``display()`` method. + +Twig also comes with a filesystem loader:: + + $loader = new Twig_Loader_Filesystem('/path/to/templates'); + $twig = new Twig_Environment($loader, array( + 'cache' => '/path/to/compilation_cache', + )); + + $template = $twig->loadTemplate('index.html'); + +.. _`download page`: http://www.twig-project.org/installation diff --git a/lib/Twig-1.0.0-RC1/doc/recipes.rst b/lib/Twig-1.0.0-RC1/doc/recipes.rst new file mode 100755 index 0000000..d913920 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/recipes.rst @@ -0,0 +1,243 @@ +Recipes +======= + +Making a Layout conditional +--------------------------- + +Working with Ajax means that the same content is sometimes displayed as is, +and sometimes decorated with a layout. As Twig layout template names can be +any valid expression, you can pass a variable that evaluates to ``true`` when +the request is made via Ajax and choose the layout accordingly: + +.. code-block:: jinja + + {% extends request.ajax ? "base_ajax.html" : "base.html" %} + + {% block content %} + This is the content to be displayed. + {% endblock %} + +Making an Include dynamic +------------------------- + +When including a template, its name does not need to be a string. For +instance, the name can depend on the value of a variable: + +.. code-block:: jinja + + {% include var ~ '_foo.html' %} + +If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be +rendered. + +As a matter of fact, the template name can be any valid expression, such as +the following: + +.. code-block:: jinja + + {% include var|default('index') ~ '_foo.html' %} + +Overriding a Template that also extends itself +---------------------------------------------- + +A template can be customized in two different ways: + +* *Inheritance*: A template *extends* a parent template and overrides some + blocks; + +* *Replacement*: If you use the filesystem loader, Twig loads the first + template if finds in a list of configured directories; a template found in a + directory *replaces* another one from a directory further in the list. + +But how do you combine both: *replace* a template that also extends itself +(aka a template in a directory further in the list)? + +Let's say that your templates are loaded from both ``.../templates/mysite`` +and ``.../templates/default`` in this order. The ``page.twig`` template, +stored in ``.../templates/default`` reads as follows: + +.. code-block:: jinja + + {# page.twig #} + {% extends "layout.twig" %} + + {% block content %} + {% endblock %} + +You can replace this template by putting a file with the same name in +``.../templates/mysite``. And if you want to extend the original template, you +might be tempted to write the following: + +.. code-block:: jinja + + {# page.twig in .../templates/mysite #} + {% extends "page.twig" %} {# from .../templates/default #} + +Of course, this will not work as Twig will always load the template from +``.../templates/mysite``. + +It turns out it is possible to get this to work, by adding a directory right +at the end of your template directories, which is the parent of all of the +other directories: ``.../templates`` in our case. This has the effect of +making every template file within our system uniquely addressable. Most of the +time you will use the "normal" paths, but in the special case of wanting to +extend a template with an overriding version of itself we can reference its +parent's full, unambiguous template path in the extends tag: + +.. code-block:: jinja + + {# page.twig in .../templates/mysite #} + {% extends "default/page.twig" %} {# from .../templates #} + +.. note:: + + This recipe was inspired by the following Django wiki page: + http://code.djangoproject.com/wiki/ExtendingTemplates + +Customizing the Syntax +---------------------- + +Twig allows some syntax customization for the block delimiters. It's not +recommended to use this feature as templates will be tied with your custom +syntax. But for specific projects, it can make sense to change the defaults. + +To change the block delimiters, you need to create your own lexer object:: + + $twig = new Twig_Environment(); + + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array('{#', '#}'), + 'tag_block' => array('{%', '%}'), + 'tag_variable' => array('{{', '}}'), + )); + $twig->setLexer($lexer); + +Here are some configuration example that simulates some other template engines +syntax:: + + // Ruby erb syntax + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array('<%#', '%>'), + 'tag_block' => array('<%', '%>'), + 'tag_variable' => array('<%=', '%>'), + )); + + // SGML Comment Syntax + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array(''), + 'tag_block' => array(''), + 'tag_variable' => array('${', '}'), + )); + + // Smarty like + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array('{*', '*}'), + 'tag_block' => array('{', '}'), + 'tag_variable' => array('{$', '}'), + )); + +Using dynamic Object Properties +------------------------------- + +When Twig encounters a variable like ``article.title``, it tries to find a +``title`` public property in the ``article`` object. + +It also works if the property does not exist but is rather defined dynamically +thanks to the magic ``__get()`` method; you just need to also implement the +``__isset()`` magic method like shown in the following snippet of code:: + + class Article + { + public function __get($name) + { + if ('title' == $name) + { + return 'The title'; + } + + // throw some kind of error + } + + public function __isset($name) + { + if ('title' == $name) + { + return true; + } + + return false; + } + } + +Accessing the parent Context in Nested Loops +-------------------------------------------- + +Sometimes, when using nested loops, you need to access the parent context. The +parent context is always accessible via the ``loop.parent`` variable. For +instance, if you have the following template data:: + + $data = array( + 'topics' => array( + 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'), + 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'), + ), + ); + +And the following template to display all messages in all topics: + +.. code-block:: jinja + + {% for topic, messages in topics %} + * {{ loop.index }}: {{ topic }} + {% for message in messages %} + - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }} + {% endfor %} + {% endfor %} + +The output will be similar to: + +.. code-block:: text + + * 1: topic1 + - 1.1: The message 1 of topic 1 + - 1.2: The message 2 of topic 1 + * 2: topic2 + - 2.1: The message 1 of topic 2 + - 2.2: The message 2 of topic 2 + +In the inner loop, the ``loop.parent`` variable is used to access the outer +context. So, the index of the current ``topic`` defined in the outer for loop +is accessible via the ``loop.parent.loop.index`` variable. + +Define undefined Functions and Filters on the Fly +------------------------------------------------- + +When a function (or a filter) is not defined, Twig defaults to throw a +``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any +valid PHP callable) which should return a function (or a filter). + +For filters, register callbacks with ``registerUndefinedFilterCallback()``. +For functions, use ``registerUndefinedFunctionCallback()``:: + + // auto-register all native PHP functions as Twig functions + // don't try this at home as it's not secure at all! + $twig->registerUndefinedFunctionCallback(function ($name) { + if (function_exists($name)) { + return new Twig_Function_Function($name); + } + + return false; + }); + +If the callable is not able to return a valid function (or filter), it must +return ``false``. + +If you register more than one callback, Twig will call them in turn until one +does not return ``false``. + +.. tip:: + + As the resolution of functions and filters is done during compilation, + there is no overhead when registering these callbacks. + +.. _callback: http://www.php.net/manual/en/function.is-callable.php diff --git a/lib/Twig-1.0.0-RC1/doc/templates.rst b/lib/Twig-1.0.0-RC1/doc/templates.rst new file mode 100755 index 0000000..efb15ac --- /dev/null +++ b/lib/Twig-1.0.0-RC1/doc/templates.rst @@ -0,0 +1,1434 @@ +Twig for Template Designers +=========================== + +This document describes the syntax and semantics of the template engine and +will be most useful as reference to those creating Twig templates. + +Synopsis +-------- + +A template is simply a text file. It can generate any text-based format (HTML, +XML, CSV, LaTeX, etc.). It doesn't have a specific extension, ``.html`` or +``.xml`` are just fine. + +A template contains **variables** or **expressions**, which get replaced with +values when the template is evaluated, and tags, which control the logic of +the template. + +Below is a minimal template that illustrates a few basics. We will cover the +details later in that document: + +.. code-block:: jinja + + + + + My Webpage + + + + +

My Webpage

+ {{ a_variable }} + + + +There are two kinds of delimiters: ``{% ... %}`` and ``{{ ... }}``. The first +one is used to execute statements such as for-loops, the latter prints the +result of an expression to the template. + +IDEs Integration +---------------- + +Modern IDEs support syntax highlighting and auto-completion for a large range +of languages. As Twig syntax is quite similar to Jinja and Django templates, +IDEs that support these two Python templating systems should also support +Twig. + +If you use Textmate, you can use the `Jinja`_ bundle or the `Django`_ one. + +If you use Vim, you can use the `Jinja syntax plugin`_. + +Variables +--------- + +The application passes variables to the templates you can mess around in the +template. Variables may have attributes or elements on them you can access +too. How a variable looks like, heavily depends on the application providing +those. + +You can use a dot (``.``) to access attributes of a variable, alternative the +so-called "subscript" syntax (``[]``) can be used. The following lines do the +same: + +.. code-block:: jinja + + {{ foo.bar }} + {{ foo['bar'] }} + +.. note:: + + It's important to know that the curly braces are *not* part of the + variable but the print statement. If you access variables inside tags + don't put the braces around. + +If a variable or attribute does not exist you will get back a ``null`` value +(which can be tested with the ``none`` expression). + +.. sidebar:: Implementation + + For convenience sake ``foo.bar`` does the following things on the PHP + layer: + + * check if ``foo`` is an array and ``bar`` a valid element; + * if not, and if ``foo`` is an object, check that ``bar`` is a valid property; + * if not, and if ``foo`` is an object, check that ``bar`` is a valid method + (even if ``bar`` is the constructor - use ``__construct()`` instead); + * if not, and if ``foo`` is an object, check that ``getBar`` is a valid method; + * if not, and if ``foo`` is an object, check that ``isBar`` is a valid method; + * if not, return a ``null`` value. + + ``foo['bar']`` on the other hand works mostly the same with the a small + difference in the order: + + * check if ``foo`` is an array and ``bar`` a valid element; + * if not, return a ``null`` value. + + Using the alternative syntax is also useful to dynamically get attributes + from arrays: + + .. code-block:: jinja + + foo[bar] + +Twig always references the following variables: + +* ``_self``: references the current template; +* ``_context``: references the current context; +* ``_charset``: references the current charset. + +Filters +------- + +Variables can by modified by **filters**. Filters are separated from the +variable by a pipe symbol (``|``) and may have optional arguments in +parentheses. Multiple filters can be chained. The output of one filter is +applied to the next. + +``{{ name|striptags|title }}`` for example will remove all HTML tags from the +``name`` and title-cases it. Filters that accept arguments have parentheses +around the arguments, like a function call. This example will join a list by +commas: ``{{ list|join(', ') }}``. + +The built-in filters section below describes all the built-in filters. + +Comments +-------- + +To comment-out part of a line in a template, use the comment syntax ``{# ... +#}``. This is useful to comment out parts of the template for debugging or to +add information for other template designers or yourself: + +.. code-block:: jinja + + {# note: disabled template because we no longer use this + {% for user in users %} + ... + {% endfor %} + #} + +Whitespace Control +------------------ + +The first newline after a template tag is removed automatically (like in PHP.) +Whitespace is not further modified by the template engine, so each whitespace +(spaces, tabs, newlines etc.) is returned unchanged. + +Use the ``spaceless`` tag to remove whitespace between HTML tags: + +.. code-block:: jinja + + {% spaceless %} +
+ foo +
+ {% endspaceless %} + + {# output will be
foo
#} + +Escaping +-------- + +It is sometimes desirable or even necessary to have Twig ignore parts it would +otherwise handle as variables or blocks. For example if the default syntax is +used and you want to use ``{{`` as raw string in the template and not start a +variable you have to use a trick. + +The easiest way is to output the variable delimiter (``{{``) by using a variable +expression: + +.. code-block:: jinja + + {{ '{{' }} + +For bigger sections it makes sense to mark a block ``raw``. For example to put +Twig syntax as example into a template you can use this snippet: + +.. code-block:: jinja + + {% raw %} +
    + {% for item in seq %} +
  • {{ item }}
  • + {% endfor %} +
+ {% endraw %} + +Template Inheritance +-------------------- + +The most powerful part of Twig is template inheritance. Template inheritance +allows you to build a base "skeleton" template that contains all the common +elements of your site and defines **blocks** that child templates can +override. + +Sounds complicated but is very basic. It's easiest to understand it by +starting with an example. + +Base Template +~~~~~~~~~~~~~ + +This template, which we'll call ``base.html``, defines a simple HTML skeleton +document that you might use for a simple two-column page. It's the job of +"child" templates to fill the empty blocks with content: + +.. code-block:: jinja + + + + + {% block head %} + + {% block title %}{% endblock %} - My Webpage + {% endblock %} + + +
{% block content %}{% endblock %}
+ + + + +In this example, the ``{% block %}`` tags define four blocks that child +templates can fill in. All the ``block`` tag does is to tell the template +engine that a child template may override those portions of the template. + +Child Template +~~~~~~~~~~~~~~ + +A child template might look like this: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% block title %}Index{% endblock %} + {% block head %} + {{ parent() }} + + {% endblock %} + {% block content %} +

Index

+

+ Welcome on my awesome homepage. +

+ {% endblock %} + +The ``{% extends %}`` tag is the key here. It tells the template engine that +this template "extends" another template. When the template system evaluates +this template, first it locates the parent. The extends tag should be the +first tag in the template. + +The filename of the template depends on the template loader. For example the +``Twig_Loader_Filesystem`` allows you to access other templates by giving the +filename. You can access templates in subdirectories with a slash: + +.. code-block:: jinja + + {% extends "layout/default.html" %} + +But this behavior can depend on the application embedding Twig. Note that +since the child template doesn't define the ``footer`` block, the value from +the parent template is used instead. + +You can't define multiple ``{% block %}`` tags with the same name in the same +template. This limitation exists because a block tag works in "both" +directions. That is, a block tag doesn't just provide a hole to fill - it also +defines the content that fills the hole in the *parent*. If there were two +similarly-named ``{% block %}`` tags in a template, that template's parent +wouldn't know which one of the blocks' content to use. + +If you want to print a block multiple times you can however use the +``block`` function: + +.. code-block:: jinja + + {% block title %}{% endblock %} +

{{ block('title') }}

+ {% block body %}{% endblock %} + +Like PHP, Twig does not support multiple inheritance. So you can only have one +extends tag called per rendering. + +Parent Blocks +~~~~~~~~~~~~~ + +It's possible to render the contents of the parent block by using the ``parent`` +function. This gives back the results of the parent block: + +.. code-block:: jinja + + {% block sidebar %} +

Table Of Contents

+ ... + {{ parent() }} + {% endblock %} + +Named Block End-Tags +~~~~~~~~~~~~~~~~~~~~ + +Twig allows you to put the name of the block after the end tag for better +readability: + +.. code-block:: jinja + + {% block sidebar %} + {% block inner_sidebar %} + ... + {% endblock inner_sidebar %} + {% endblock sidebar %} + +However the name after the ``endblock`` word must match the block name. + +Block Nesting and Scope +~~~~~~~~~~~~~~~~~~~~~~~ + +Blocks can be nested for more complex layouts. Per default, blocks have access +to variables from outer scopes: + +.. code-block:: jinja + + {% for item in seq %} +
  • {% block loop_item %}{{ item }}{% endblock %}
  • + {% endfor %} + +Block Shortcuts +~~~~~~~~~~~~~~~ + +For blocks with few content, it's possible to have a shortcut syntax. The +following constructs do the same: + +.. code-block:: jinja + + {% block title %} + {{ page_title|title }} + {% endblock %} + +.. code-block:: jinja + + {% block title page_title|title %} + +Dynamic Inheritance +~~~~~~~~~~~~~~~~~~~ + +Twig supports dynamic inheritance by using a variable as the base template: + +.. code-block:: jinja + + {% extends some_var %} + +If the variable evaluates to a ``Twig_Template`` object, Twig will use it as +the parent template:: + + // {% extends layout %} + + $layout = $twig->loadTemplate('some_layout_template.twig'); + + $twig->display('template.twig', array('layout' => $layout)); + +Conditional Inheritance +~~~~~~~~~~~~~~~~~~~~~~~ + +As a matter of fact, the template name can be any valid expression. So, it's +also possible to make the inheritance mechanism conditional: + +.. code-block:: jinja + + {% extends standalone ? "minimum.html" : "base.html" %} + +In this example, the template will extend the "minimum.html" layout template +if the ``standalone`` variable evaluates to ``true``, and "base.html" +otherwise. + +Import Context Behavior +----------------------- + +Per default included templates are passed the current context. + +The context that is passed to the included template includes variables defined +in the template: + +.. code-block:: jinja + + {% for box in boxes %} + {% include "render_box.html" %} + {% endfor %} + +The included template ``render_box.html`` is able to access ``box``. + +HTML Escaping +------------- + +When generating HTML from templates, there's always a risk that a variable +will include characters that affect the resulting HTML. There are two +approaches: manually escaping each variable or automatically escaping +everything by default. + +Twig supports both, automatic escaping is enabled by default. + +.. note:: + + Automatic escaping is only supported if the *escaper* extension has been + enabled (which is the default). + +Working with Manual Escaping +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If manual escaping is enabled it's **your** responsibility to escape variables +if needed. What to escape? If you have a variable that *may* include any of +the following chars (``>``, ``<``, ``&``, or ``"``) you **have to** escape it unless +the variable contains well-formed and trusted HTML. Escaping works by piping +the variable through the ``|e`` filter: ``{{ user.username|e }}``. + +Working with Automatic Escaping +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whether automatic escaping is enabled or not, you can mark a section of a +template to be escaped or not by using the ``autoescape`` tag: + +.. code-block:: jinja + + {% autoescape true %} + Everything will be automatically escaped in this block + {% endautoescape %} + + {% autoescape false %} + Everything will be outputed as is in this block + {% endautoescape %} + + {% autoescape true js %} + Everything will be automatically escaped in this block + using the js escaping strategy + {% endautoescape %} + +When automatic escaping is enabled everything is escaped by default except for +values explicitly marked as safe. Those can be marked in the template by using +the ``|raw`` filter. + +Functions returning template data (like macros and ``parent``) always return +safe markup. + +.. note:: + + Twig is smart enough to not escape an already escaped value by the + ``escape`` filter. + +.. note:: + + The chapter for developers give more information about when and how + automatic escaping is applied. + +List of Control Structures +-------------------------- + +A control structure refers to all those things that control the flow of a +program - conditionals (i.e. ``if``/``elseif``/``else``), ``for``-loops, as well as +things like blocks. Control structures appear inside ``{% ... %}`` blocks. + +For +~~~ + +Loop over each item in a sequence. For example, to display a list of users +provided in a variable called ``users``: + +.. code-block:: jinja + +

    Members

    +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    + +.. note:: + + A sequence can be either an array or an object implementing the + ``Traversable`` interface. + +If you do need to iterate over a sequence of numbers, you can use the ``..`` +operator: + +.. code-block:: jinja + + {% for i in 0..10 %} + * {{ i }} + {% endfor %} + +The above snippet of code would print all numbers from 0 to 10. + +It can be also useful with letters: + +.. code-block:: jinja + + {% for letter in 'a'..'z' %} + * {{ letter }} + {% endfor %} + +The ``..`` operator can take any expression at both sides: + +.. code-block:: jinja + + {% for letter in 'a'|upper..'z'|upper %} + * {{ letter }} + {% endfor %} + +If you need a step different from 1, you can use the ``range`` function +instead: + +.. code-block:: jinja + + {% for i in range(0, 10, 2) %} + * {{ i }} + {% endfor %} + +Inside of a ``for`` loop block you can access some special variables: + +===================== ============================================================= +Variable Description +===================== ============================================================= +``loop.index`` The current iteration of the loop. (1 indexed) +``loop.index0`` The current iteration of the loop. (0 indexed) +``loop.revindex`` The number of iterations from the end of the loop (1 indexed) +``loop.revindex0`` The number of iterations from the end of the loop (0 indexed) +``loop.first`` True if first iteration +``loop.last`` True if last iteration +``loop.length`` The number of items in the sequence +``loop.parent`` The parent context +===================== ============================================================= + +.. note:: + + The ``loop.length``, ``loop.revindex``, ``loop.revindex0``, and + ``loop.last`` variables are only available for PHP arrays, or objects that + implement the ``Countable`` interface. + +.. note:: + + Unlike in PHP it's not possible to ``break`` or ``continue`` in a loop. + +If no iteration took place because the sequence was empty, you can render a +replacement block by using ``else``: + +.. code-block:: jinja + +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% else %} +
    • no user found
    • + {% endfor %} +
    + +By default, a loop iterates over the values of the sequence. You can iterate +on keys by using the ``keys`` filter: + +.. code-block:: jinja + +

    Members

    +
      + {% for key in users|keys %} +
    • {{ key }}
    • + {% endfor %} +
    + +You can also access both keys and values: + +.. code-block:: jinja + +

    Members

    +
      + {% for key, user in users %} +
    • {{ key }}: {{ user.username|e }}
    • + {% endfor %} +
    + +If +~~ + +The ``if`` statement in Twig is comparable with the if statements of PHP. In +the simplest form you can use it to test if a variable is not empty: + +.. code-block:: jinja + + {% if users %} +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    + {% endif %} + +.. note:: + + If you want to test if the variable is defined, use ``if users is + defined`` instead. + +For multiple branches ``elseif`` and ``else`` can be used like in PHP. You can use +more complex ``expressions`` there too: + +.. code-block:: jinja + + {% if kenny.sick %} + Kenny is sick. + {% elseif kenny.dead %} + You killed Kenny! You bastard!!! + {% else %} + Kenny looks okay --- so far + {% endif %} + +Macros +~~~~~~ + +Macros are comparable with functions in regular programming languages. They +are useful to put often used HTML idioms into reusable elements to not repeat +yourself. + +Here is a small example of a macro that renders a form element: + +.. code-block:: jinja + + {% macro input(name, value, type, size) %} + + {% endmacro %} + +Macros differs from native PHP functions in a few ways: + +* Default argument values are defined by using the ``default`` filter in the + macro body; + +* Arguments of a macro are always optional. + +But as PHP functions, macros don't have access to the current template +variables. + +.. tip:: + + You can pass the whole context as an argument by using the special + ``_context`` variable. + +Macros can be defined in any template, and need to be "imported" before being +used (see the Import section for more information): + +.. code-block:: jinja + + {% import "forms.html" as forms %} + +The above ``import`` call imports the "forms.html" file (which can contain only +macros, or a template and some macros), and import the functions as items of +the ``forms`` variable. + +The macro can then be called at will: + +.. code-block:: jinja + +

    {{ forms.input('username') }}

    +

    {{ forms.input('password', none, 'password') }}

    + +If macros are defined and used in the same template, you can use the +special ``_self`` variable, without importing them: + +.. code-block:: jinja + +

    {{ _self.input('username') }}

    + +When you want to use a macro in another one from the same file, use the ``_self`` +variable: + +.. code-block:: jinja + + {% macro input(name, value, type, size) %} + + {% endmacro %} + + {% macro wrapped_input(name, value, type, size) %} +
    + {{ _self.input(name, value, type, size) }} +
    + {% endmacro %} + +When the macro is defined in another file, you need to import it: + +.. code-block:: jinja + + {# forms.html #} + + {% macro input(name, value, type, size) %} + + {% endmacro %} + + {# shortcuts.html #} + + {% macro wrapped_input(name, value, type, size) %} + {% import "forms.html" as forms %} +
    + {{ forms.input(name, value, type, size) }} +
    + {% endmacro %} + +Filters +~~~~~~~ + +Filter sections allow you to apply regular Twig filters on a block of template +data. Just wrap the code in the special ``filter`` section: + +.. code-block:: jinja + + {% filter upper %} + This text becomes uppercase + {% endfilter %} + +You can also chain filters: + +.. code-block:: jinja + + {% filter lower|escape %} + SOME TEXT + {% endfilter %} + +It should returns ``<strong>some text</strong>``. + +Assignments +~~~~~~~~~~~ + +Inside code blocks you can also assign values to variables. Assignments use +the ``set`` tag and can have multiple targets: + +.. code-block:: jinja + + {% set foo = 'foo' %} + + {% set foo = [1, 2] %} + + {% set foo = {'foo': 'bar'} %} + + {% set foo = 'foo' ~ 'bar' %} + + {% set foo, bar = 'foo', 'bar' %} + +The ``set`` tag can also be used to 'capture' chunks of HTML: + +.. code-block:: jinja + + {% set foo %} + + {% endset %} + +Extends +~~~~~~~ + +The ``extends`` tag can be used to extend a template from another one. You can +have multiple of them in a file but only one of them may be executed at the +time. There is no support for multiple inheritance. See the section about +Template inheritance above for more information. + +Block +~~~~~ + +Blocks are used for inheritance and act as placeholders and replacements at +the same time. They are documented in detail as part of the section about +Template inheritance above. + +Include +~~~~~~~ + +The ``include`` statement is useful to include a template and return the +rendered content of that file into the current namespace: + +.. code-block:: jinja + + {% include 'header.html' %} + Body + {% include 'footer.html' %} + +Included templates have access to the variables of the active context. + +You can add additional variables by passing them after the ``with`` keyword: + +.. code-block:: jinja + + {# the foo template will have access to the variables from the current context and the foo one #} + {% include 'foo' with {'foo': 'bar'} %} + + {% set vars = {'foo': 'bar'} %} + {% include 'foo' with vars %} + +You can disable access to the context by appending the ``only`` keyword: + +.. code-block:: jinja + + {# only the foo variable will be accessible #} + {% include 'foo' with {'foo': 'bar'} only %} + +.. code-block:: jinja + + {# no variable will be accessible #} + {% include 'foo' only %} + +.. tip:: + + When including a template created by an end user, you should consider + sandboxing it. More information in the "Twig for Developers" chapter. + +The template name can be any valid Twig expression: + +.. code-block:: jinja + + {% include some_var %} + {% include ajax ? 'ajax.html' : 'not_ajax.html' %} + +And if the expression evaluates to a ``Twig_Template`` object, Twig will use it +directly:: + + // {% include template %} + + $template = $twig->loadTemplate('some_template.twig'); + + $twig->loadTemplate('template.twig')->display(array('template' => $template)); + +Import +~~~~~~ + +Twig supports putting often used code into macros. These macros can go into +different templates and get imported from there. + +There are two ways to import templates. You can import the complete template +into a variable or request specific macros from it. + +Imagine we have a helper module that renders forms (called ``forms.html``): + +.. code-block:: jinja + + {% macro input(name, value, type, size) %} + + {% endmacro %} + + {% macro textarea(name, value, rows) %} + + {% endmacro %} + +The easiest and most flexible is importing the whole module into a variable. +That way you can access the attributes: + +.. code-block:: jinja + + {% import 'forms.html' as forms %} + +
    +
    Username
    +
    {{ forms.input('username') }}
    +
    Password
    +
    {{ forms.input('password', none, 'password') }}
    +
    +

    {{ forms.textarea('comment') }}

    + +Alternatively you can import names from the template into the current +namespace: + +.. code-block:: jinja + + {% from 'forms.html' import input as input_field, textarea %} + +
    +
    Username
    +
    {{ input_field('username') }}
    +
    Password
    +
    {{ input_field('password', type='password') }}
    +
    +

    {{ textarea('comment') }}

    + +Importing is not needed if the macros and the template are defined in the same +file; use the special ``_self`` variable instead: + +.. code-block:: jinja + + {# index.html template #} + + {% macro textarea(name, value, rows) %} + + {% endmacro %} + +

    {{ _self.textarea('comment') }}

    + +But you can still create an alias by importing from the ``_self`` variable: + +.. code-block:: jinja + + {# index.html template #} + + {% macro textarea(name, value, rows) %} + + {% endmacro %} + + {% import _self as forms %} + +

    {{ forms.textarea('comment') }}

    + +Expressions +----------- + +Twig allows basic expressions everywhere. These work very similar to regular +PHP and even if you're not working with PHP you should feel comfortable with +it. + +The operator precedence is as follows, with the lowest-precedence operators +listed first: ``or``, ``and``, ``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``+``, ``-``, +``~``, ``*``, ``/``, ``%``, ``//``, ``is``, ``..``, and ``**``. + +Literals +~~~~~~~~ + +The simplest form of expressions are literals. Literals are representations +for PHP types such as strings, numbers, and arrays. The following literals +exist: + +* ``"Hello World"``: Everything between two double or single quotes is a + string. They are useful whenever you need a string in the template (for + example as arguments to function calls, filters or just to extend or + include a template). + +* ``42`` / ``42.23``: Integers and floating point numbers are created by just + writing the number down. If a dot is present the number is a float, + otherwise an integer. + +* ``["foo", "bar"]``: Arrays are defined by a sequence of expressions + separated by a comma (``,``) and wrapped with squared brackets (``[]``). + +* ``{"foo": "bar"}``: Hashes are defined by a list of keys and values + separated by a comma (``,``) and wrapped with curly braces (``{}``). A value + can be any valid expression. + +* ``true`` / ``false``: ``true`` represents the true value, ``false`` + represents the false value. + +* ``none``: ``none`` represents no specific value (the equivalent of ``null`` in + PHP). This is the value returned when a variable does not exist. + +Arrays and hashes can be nested: + +.. code-block:: jinja + + {% set foo = [1, {"foo": "bar"}] %} + +Math +~~~~ + +Twig allows you to calculate with values. This is rarely useful in templates +but exists for completeness' sake. The following operators are supported: + +* ``+``: Adds two objects together (the operands are casted to numbers). ``{{ + 1 + 1 }}`` is ``2``. + +* ``-``: Substracts the second number from the first one. ``{{ 3 - 2 }}`` is + ``1``. + +* ``/``: Divides two numbers. The return value will be a floating point + number. ``{{ 1 / 2 }}`` is ``{{ 0.5 }}``. + +* ``%``: Calculates the remainder of an integer division. ``{{ 11 % 7 }}`` is + ``4``. + +* ``//``: Divides two numbers and returns the truncated integer result. ``{{ + 20 // 7 }}`` is ``2``. + +* ``*``: Multiplies the left operand with the right one. ``{{ 2 * 2 }}`` would + return ``4``. + +* ``**``: Raises the left operand to the power of the right operand. ``{{ 2**3 + }}`` would return ``8``. + +Logic +~~~~~ + +For ``if`` statements, ``for`` filtering or ``if`` expressions it can be useful to +combine multiple expressions: + +* ``and``: Returns true if the left and the right operands are both true. + +* ``or``: Returns true if the left or the right operand is true. + +* ``not``: Negates a statement. + +* ``(expr)``: Groups an expression. + +Comparisons +~~~~~~~~~~~ + +The following comparison operators are supported in any expression: ``==``, +``!=``, ``<``, ``>``, ``>=``, and ``<=``. + +Containment Operator +~~~~~~~~~~~~~~~~~~~~ + +The ``in`` operator performs containment test. + +It returns ``true`` if the left operand is contained in the right: + +.. code-block:: jinja + + {# returns true #} + + {{ 1 in [1, 2, 3] }} + + {{ 'cd' in 'abcde' }} + +.. tip:: + + You can use this filter to perform a containment test on strings, arrays, + or objects implementing the ``Traversable`` interface. + +To perform a negative test, use the ``not in`` operator: + +.. code-block:: jinja + + {% if 1 not in [1, 2, 3] %} + + {# is equivalent to #} + {% if not (1 in [1, 2, 3]) %} + +Tests +~~~~~ + +The ``is`` operator performs tests. Tests can be used to test a variable against +a common expression. The right operand is name of the test: + +.. code-block:: jinja + + {# find out if a variable is odd #} + + {{ name is odd }} + +Tests can accept arguments too: + +.. code-block:: jinja + + {% if loop.index is divisibleby(3) %} + +Tests can be negated by using the ``not in`` operator: + +.. code-block:: jinja + + {% if loop.index is not divisibleby(3) %} + + {# is equivalent to #} + {% if not (loop.index is divisibleby(3)) %} + +The built-in tests section below describes all the built-in tests. + +Other Operators +~~~~~~~~~~~~~~~ + +The following operators are very useful but don't fit into any of the other +two categories: + +* ``..``: Creates a sequence based on the operand before and after the + operator (see the ``for`` tag for some usage examples). + +* ``|``: Applies a filter. + +* ``~``: Converts all operands into strings and concatenates them. ``{{ "Hello + " ~ name ~ "!" }}`` would return (assuming ``name`` is ``'John'``) ``Hello + John!``. + +* ``.``, ``[]``: Gets an attribute of an object. + +* ``?:``: Twig supports the PHP ternary operator: + + .. code-block:: jinja + + {{ foo ? 'yes' : 'no' }} + +List of built-in Filters +------------------------ + +``date`` +~~~~~~~~ + +The ``date`` filter is able to format a date to a given format: + +.. code-block:: jinja + + {{ post.published_at|date("m/d/Y") }} + +The ``date`` filter accepts any date format supported by `DateTime`_ and +``DateTime`` instances. For instance, to display the current date, filter the +word "now": + +.. code-block:: jinja + + {{ "now"|date("m/d/Y") }} + +``format`` +~~~~~~~~~~ + +The ``format`` filter formats a given string by replacing the placeholders +(placeholders follows the ``printf`` notation): + +.. code-block:: jinja + + {{ "I like %s and %s."|format(foo, "bar") }} + + {# returns I like foo and bar. (if the foo parameter equals to the foo string) #} + +``replace`` +~~~~~~~~~~~ + +The ``replace`` filter formats a given string by replacing the placeholders +(placeholders are free-form): + +.. code-block:: jinja + + {{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }} + + {# returns I like foo and bar. (if the foo parameter equals to the foo string) #} + +``url_encode`` +~~~~~~~~~~~~~~ + +The ``url_encode`` filter URL encodes a given string. + +``json_encode`` +~~~~~~~~~~~~~~~ + +The ``json_encode`` filter returns the JSON representation of a string. + +``title`` +~~~~~~~~~ + +The ``title`` filter returns a titlecased version of the value. I.e. words will +start with uppercase letters, all remaining characters are lowercase. + +``capitalize`` +~~~~~~~~~~~~~~ + +The ``capitalize`` filter capitalizes a value. The first character will be +uppercase, all others lowercase. + +``upper`` +~~~~~~~~~ + +The ``upper`` filter converts a value to uppercase. + +``lower`` +~~~~~~~~~ + +The ``lower`` filter converts a value to lowercase. + +``striptags`` +~~~~~~~~~~~~~ + +The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace by +one space. + +``join`` +~~~~~~~~ + +The ``join`` filter returns a string which is the concatenation of the strings +in the sequence. The separator between elements is an empty string per +default, you can define it with the optional parameter: + +.. code-block:: jinja + + {{ [1, 2, 3]|join('|') }} + {# returns 1|2|3 #} + + {{ [1, 2, 3]|join }} + {# returns 123 #} + +``reverse`` +~~~~~~~~~~~ + +The ``reverse`` filter reverses an array or an object if it implements the +``Iterator`` interface. + +``length`` +~~~~~~~~~~ + +The ``length`` filters returns the number of items of a sequence or mapping, or +the length of a string. + +``sort`` +~~~~~~~~ + +The ``sort`` filter sorts an array. + +``default`` +~~~~~~~~~~~ + +The ``default`` filter returns the passed default value if the value is +undefined or empty, otherwise the value of the variable: + +.. code-block:: jinja + + {{ var|default('var is not defined') }} + + {{ var.foo|default('foo item on var is not defined') }} + + {{ ''|default('passed var is empty') }} + +.. note:: + + Read the documentation for the ``defined`` and ``empty`` tests below to + learn more about their semantics. + +``keys`` +~~~~~~~~ + +The ``keys`` filter returns the keys of an array. It is useful when you want to +iterate over the keys of an array: + +.. code-block:: jinja + + {% for key in array|keys %} + ... + {% endfor %} + +``escape``, ``e`` +~~~~~~~~~~~~~~~~~ + +The ``escape`` filter converts the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in +strings to HTML-safe sequences. Use this if you need to display text that +might contain such characters in HTML. + +.. note:: + + Internally, ``escape`` uses the PHP ``htmlspecialchars`` function. + +``raw`` +~~~~~~~ + +The ``raw`` filter marks the value as safe which means that in an environment +with automatic escaping enabled this variable will not be escaped if ``raw`` is +the last filter applied to it. + +.. code-block:: jinja + + {% autoescape true } + {{ var|raw }} {# var won't be escaped #} + {% endautoescape %} + +``merge`` +~~~~~~~~~ + +The ``merge`` filter merges an array or a hash with the value: + +.. code-block:: jinja + + {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} + + {% set items = items|merge({ 'peugeot': 'car' }) %} + + {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} + +List of built-in Tests +---------------------- + +``divisibleby`` +~~~~~~~~~~~~~~~ + +``divisibleby`` checks if a variable is divisible by a number: + +.. code-block:: jinja + + {% if loop.index is divisibleby(3) %} + +``none`` +~~~~~~~~ + +``none`` returns ``true`` if the variable is ``none``: + +.. code-block:: jinja + + {{ var is none }} + +``even`` +~~~~~~~~ + +``even`` returns ``true`` if the given number is even: + +.. code-block:: jinja + + {{ var is even }} + +``odd`` +~~~~~~~ + +``odd`` returns ``true`` if the given number is odd: + +.. code-block:: jinja + + {{ var is odd }} + +``sameas`` +~~~~~~~~~~ + +``sameas`` checks if a variable points to the same memory address than another +variable: + +.. code-block:: jinja + + {% if foo.attribute is sameas(false) %} + the foo attribute really is the ``false`` PHP value + {% endif %} + +``constant`` +~~~~~~~~~~~~ + +``constant`` checks if a variable has the exact same value as a constant. You +can use either global constants or class constants: + +.. code-block:: jinja + + {% if post.status is constant('Post::PUBLISHED') %} + the status attribute is exactly the same as Post::PUBLISHED + {% endif %} + +``defined`` +~~~~~~~~~~~ + +``defined`` checks if a variable is defined in the current context. This is very +useful if you use the ``strict_variables`` option: + +.. code-block:: jinja + + {# defined works with variable names #} + {% if foo is defined %} + ... + {% endif %} + + {# and attributes on variables names #} + {% if foo.bar is defined %} + ... + {% endif %} + +``empty`` +~~~~~~~~~ + +``empty`` checks if a variable is empty: + +.. code-block:: jinja + + {# evaluates to true if the foo variable is null, false, or the empty string #} + {% if foo is empty %} + ... + {% endif %} + +List of Global Functions +------------------------ + +The following functions are available in the global scope by default: + +``range`` +~~~~~~~~~ + +Returns a list containing an arithmetic progression of integers. When step is +given, it specifies the increment (or decrement): + +.. code-block:: jinja + + {% for i in range(0, 3) %} + {{ i }}, + {% endfor %} + + {# returns 0, 1, 2, 3 #} + + {% for i in range(0, 6, 2) %} + {{ i }}, + {% endfor %} + + {# returns 0, 2, 4, 6 #} + +.. tip:: + + The ``range`` function works as the native PHP ``range`` function. + +The ``..`` operator is a syntactic sugar for the ``range`` function (with a +step of 1): + +.. code-block:: jinja + + {% for i in 0..10 %} + {{ i }}, + {% endfor %} + +``cycle`` +~~~~~~~~~ + +The ``cycle`` function can be used to cycle on an array of values: + +.. code-block:: jinja + + {% for i in 0..10 %} + {{ cycle(['odd', 'even'], i) }} + {% endfor %} + +The array can contain any number of values: + +.. code-block:: jinja + + {% set fruits = ['apple', 'orange', 'citrus'] %} + + {% for i in 0..10 %} + {{ cycle(fruits, i) }} + {% endfor %} + +``constant`` +~~~~~~~~~~~~ + +``constant`` returns the constant value for a given string: + +.. code-block:: jinja + + {{ some_date|date(constant('DATE_W3C')) }} + +Extensions +---------- + +Twig can be easily extended. If you are looking for new tags or filters, have +a look at the Twig official extension repository: +http://github.com/fabpot/Twig-extensions. + +.. _`Jinja`: http://jinja.pocoo.org/2/documentation/integration +.. _`Django`: http://code.djangoproject.com/wiki/TextMate +.. _`Jinja syntax plugin`: http://jinja.pocoo.org/2/documentation/integration +.. _`DateTime`: http://www.php.net/manual/en/datetime.construct.php diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Autoloader.php b/lib/Twig-1.0.0-RC1/lib/Twig/Autoloader.php new file mode 100755 index 0000000..4d1f6e1 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Autoloader.php @@ -0,0 +1,46 @@ + + */ +class Twig_Autoloader +{ + /** + * Registers Twig_Autoloader as an SPL autoloader. + */ + static public function register() + { + ini_set('unserialize_callback_func', 'spl_autoload_call'); + spl_autoload_register(array(new self, 'autoload')); + } + + /** + * Handles autoloading of classes. + * + * @param string $class A class name. + * + * @return boolean Returns true if the class has been loaded + */ + static public function autoload($class) + { + if (0 !== strpos($class, 'Twig')) { + return; + } + + if (file_exists($file = dirname(__FILE__).'/../'.str_replace('_', '/', $class).'.php')) { + require $file; + } + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Compiler.php b/lib/Twig-1.0.0-RC1/lib/Twig/Compiler.php new file mode 100755 index 0000000..aef6a87 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Compiler.php @@ -0,0 +1,220 @@ + + */ +class Twig_Compiler implements Twig_CompilerInterface +{ + protected $lastLine; + protected $source; + protected $indentation; + protected $env; + + /** + * Constructor. + * + * @param Twig_Environment $env The twig environment instance + */ + public function __construct(Twig_Environment $env) + { + $this->env = $env; + } + + /** + * Returns the environment instance related to this compiler. + * + * @return Twig_Environment The environment instance + */ + public function getEnvironment() + { + return $this->env; + } + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + public function getSource() + { + return $this->source; + } + + /** + * Compiles a node. + * + * @param Twig_NodeInterface $node The node to compile + * @param integer $indent The current indentation + * + * @return Twig_Compiler The current compiler instance + */ + public function compile(Twig_NodeInterface $node, $indentation = 0) + { + $this->lastLine = null; + $this->source = ''; + $this->indentation = $indentation; + + $node->compile($this); + + return $this; + } + + public function subcompile(Twig_NodeInterface $node, $raw = true) + { + if (false === $raw) + { + $this->addIndentation(); + } + + $node->compile($this); + + return $this; + } + + /** + * Adds a raw string to the compiled code. + * + * @param string $string The string + * + * @return Twig_Compiler The current compiler instance + */ + public function raw($string) + { + $this->source .= $string; + + return $this; + } + + /** + * Writes a string to the compiled code by adding indentation. + * + * @return Twig_Compiler The current compiler instance + */ + public function write() + { + $strings = func_get_args(); + foreach ($strings as $string) { + $this->addIndentation(); + $this->source .= $string; + } + + return $this; + } + + public function addIndentation() + { + $this->source .= str_repeat(' ', $this->indentation * 4); + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @param string $string The string + * + * @return Twig_Compiler The current compiler instance + */ + public function string($value) + { + $this->source .= sprintf('"%s"', addcslashes($value, "\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @param mixed $value The value to convert + * + * @return Twig_Compiler The current compiler instance + */ + public function repr($value) + { + if (is_int($value) || is_float($value)) { + $this->raw($value); + } else if (null === $value) { + $this->raw('null'); + } else if (is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } else if (is_array($value)) { + $this->raw('array('); + $i = 0; + foreach ($value as $key => $value) { + if ($i++) { + $this->raw(', '); + } + $this->repr($key); + $this->raw(' => '); + $this->repr($value); + } + $this->raw(')'); + } else { + $this->string($value); + } + + return $this; + } + + /** + * Adds debugging information. + * + * @param Twig_NodeInterface $node The related twig node + * + * @return Twig_Compiler The current compiler instance + */ + public function addDebugInfo(Twig_NodeInterface $node) + { + if ($node->getLine() != $this->lastLine) { + $this->lastLine = $node->getLine(); + $this->write("// line {$node->getLine()}\n"); + } + + return $this; + } + + /** + * Indents the generated code. + * + * @param integer $indent The number of indentation to add + * + * @return Twig_Compiler The current compiler instance + */ + public function indent($step = 1) + { + $this->indentation += $step; + + return $this; + } + + /** + * Outdents the generated code. + * + * @param integer $indent The number of indentation to remove + * + * @return Twig_Compiler The current compiler instance + */ + public function outdent($step = 1) + { + $this->indentation -= $step; + + if ($this->indentation < 0) { + throw new Twig_Error('Unable to call outdent() as the indentation would become negative'); + } + + return $this; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/CompilerInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/CompilerInterface.php new file mode 100755 index 0000000..c03407e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/CompilerInterface.php @@ -0,0 +1,35 @@ + + */ +interface Twig_CompilerInterface +{ + /** + * Compiles a node. + * + * @param Twig_NodeInterface $node The node to compile + * + * @return Twig_Compiler The current compiler instance + */ + function compile(Twig_NodeInterface $node); + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + function getSource(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Environment.php b/lib/Twig-1.0.0-RC1/lib/Twig/Environment.php new file mode 100755 index 0000000..891453e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Environment.php @@ -0,0 +1,893 @@ + + */ +class Twig_Environment +{ + const VERSION = '1.0.0-RC1'; + + protected $charset; + protected $loader; + protected $debug; + protected $autoReload; + protected $cache; + protected $lexer; + protected $parser; + protected $compiler; + protected $baseTemplateClass; + protected $extensions; + protected $parsers; + protected $visitors; + protected $filters; + protected $tests; + protected $functions; + protected $globals; + protected $runtimeInitialized; + protected $loadedTemplates; + protected $strictVariables; + protected $unaryOperators; + protected $binaryOperators; + protected $templateClassPrefix = '__TwigTemplate_'; + protected $functionCallbacks; + protected $filterCallbacks; + + /** + * Constructor. + * + * Available options: + * + * * debug: When set to `true`, the generated templates have a __toString() + * method that you can use to display the generated nodes (default to + * false). + * + * * charset: The charset used by the templates (default to utf-8). + * + * * base_template_class: The base template class to use for generated + * templates (default to Twig_Template). + * + * * cache: An absolute path where to store the compiled templates, or + * false to disable compilation cache (default) + * + * * auto_reload: Whether to reload the template is the original source changed. + * If you don't provide the auto_reload option, it will be + * determined automatically base on the debug value. + * + * * strict_variables: Whether to ignore invalid variables in templates + * (default to false). + * + * * autoescape: Whether to enable auto-escaping (default to true); + * + * * optimizations: A flag that indicates which optimizations to apply + * (default to -1 which means that all optimizations are enabled; + * set it to 0 to disable) + * + * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance + * @param array $options An array of options + */ + public function __construct(Twig_LoaderInterface $loader = null, $options = array()) + { + if (null !== $loader) { + $this->setLoader($loader); + } + + $options = array_merge(array( + 'debug' => false, + 'charset' => 'UTF-8', + 'base_template_class' => 'Twig_Template', + 'strict_variables' => false, + 'autoescape' => true, + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, + ), $options); + + $this->debug = (bool) $options['debug']; + $this->charset = $options['charset']; + $this->baseTemplateClass = $options['base_template_class']; + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->extensions = array( + 'core' => new Twig_Extension_Core(), + 'escaper' => new Twig_Extension_Escaper((bool) $options['autoescape']), + 'optimizer' => new Twig_Extension_Optimizer($options['optimizations']), + ); + $this->strictVariables = (bool) $options['strict_variables']; + $this->runtimeInitialized = false; + $this->setCache($options['cache']); + $this->functionCallbacks = array(); + $this->filterCallbacks = array(); + } + + /** + * Gets the base template class for compiled templates. + * + * @return string The base template class name + */ + public function getBaseTemplateClass() + { + return $this->baseTemplateClass; + } + + /** + * Sets the base template class for compiled templates. + * + * @param string $class The base template class name + */ + public function setBaseTemplateClass($class) + { + $this->baseTemplateClass = $class; + } + + /** + * Enables debugging mode. + */ + public function enableDebug() + { + $this->debug = true; + } + + /** + * Disables debugging mode. + */ + public function disableDebug() + { + $this->debug = false; + } + + /** + * Checks if debug mode is enabled. + * + * @return Boolean true if debug mode is enabled, false otherwise + */ + public function isDebug() + { + return $this->debug; + } + + /** + * Enables the auto_reload option. + */ + public function enableAutoReload() + { + $this->autoReload = true; + } + + /** + * Disables the auto_reload option. + */ + public function disableAutoReload() + { + $this->autoReload = false; + } + + /** + * Checks if the auto_reload option is enabled. + * + * @return Boolean true if auto_reload is enabled, false otherwise + */ + public function isAutoReload() + { + return $this->autoReload; + } + + /** + * Enables the strict_variables option. + */ + public function enableStrictVariables() + { + $this->strictVariables = true; + } + + /** + * Disables the strict_variables option. + */ + public function disableStrictVariables() + { + $this->strictVariables = false; + } + + /** + * Checks if the strict_variables option is enabled. + * + * @return Boolean true if strict_variables is enabled, false otherwise + */ + public function isStrictVariables() + { + return $this->strictVariables; + } + + /** + * Gets the cache directory or false if cache is disabled. + * + * @return string|false + */ + public function getCache() + { + return $this->cache; + } + + /** + * Sets the cache directory or false if cache is disabled. + * + * @param string|false $cache The absolute path to the compiled templates, + * or false to disable cache + */ + public function setCache($cache) + { + $this->cache = $cache ? $cache : false; + } + + /** + * Gets the cache filename for a given template. + * + * @param string $name The template name + * + * @return string The cache file name + */ + public function getCacheFilename($name) + { + if (false === $this->cache) { + return false; + } + + $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix)); + + return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php'; + } + + /** + * Gets the template class associated with the given string. + * + * @param string $name The name for which to calculate the template class name + * + * @return string The template class name + */ + public function getTemplateClass($name) + { + return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)); + } + + /** + * Gets the template class prefix. + * + * @return string The template class prefix + */ + public function getTemplateClassPrefix() + { + return $this->templateClassPrefix; + } + + /** + * Loads a template by name. + * + * @param string $name The template name + * + * @return Twig_TemplateInterface A template instance representing the given template name + */ + public function loadTemplate($name) + { + $cls = $this->getTemplateClass($name); + + if (isset($this->loadedTemplates[$cls])) { + return $this->loadedTemplates[$cls]; + } + + if (!class_exists($cls, false)) { + if (false === $cache = $this->getCacheFilename($name)) { + eval('?>'.$this->compileSource($this->loader->getSource($name), $name)); + } else { + if (!file_exists($cache) || ($this->isAutoReload() && !$this->loader->isFresh($name, filemtime($cache)))) { + $this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name)); + } + + require_once $cache; + } + } + + if (!$this->runtimeInitialized) { + $this->initRuntime(); + } + + return $this->loadedTemplates[$cls] = new $cls($this); + } + + /** + * Clears the internal template cache. + */ + public function clearTemplateCache() + { + $this->loadedTemplates = array(); + } + + /** + * Clears the template cache files on the filesystem. + */ + public function clearCacheFiles() + { + if (false === $this->cache) { + return; + } + + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($file->isFile()) { + @unlink($file->getPathname()); + } + } + } + + /** + * Gets the Lexer instance. + * + * @return Twig_LexerInterface A Twig_LexerInterface instance + */ + public function getLexer() + { + if (null === $this->lexer) { + $this->lexer = new Twig_Lexer($this); + } + + return $this->lexer; + } + + /** + * Sets the Lexer instance. + * + * @param Twig_LexerInterface A Twig_LexerInterface instance + */ + public function setLexer(Twig_LexerInterface $lexer) + { + $this->lexer = $lexer; + } + + /** + * Tokenizes a source code. + * + * @param string $source The template source code + * @param string $name The template name + * + * @return Twig_TokenStream A Twig_TokenStream instance + */ + public function tokenize($source, $name = null) + { + return $this->getLexer()->tokenize($source, $name); + } + + /** + * Gets the Parser instance. + * + * @return Twig_ParserInterface A Twig_ParserInterface instance + */ + public function getParser() + { + if (null === $this->parser) { + $this->parser = new Twig_Parser($this); + } + + return $this->parser; + } + + /** + * Sets the Parser instance. + * + * @param Twig_ParserInterface A Twig_ParserInterface instance + */ + public function setParser(Twig_ParserInterface $parser) + { + $this->parser = $parser; + } + + /** + * Parses a token stream. + * + * @param Twig_TokenStream $tokens A Twig_TokenStream instance + * + * @return Twig_Node_Module A Node tree + */ + public function parse(Twig_TokenStream $tokens) + { + return $this->getParser()->parse($tokens); + } + + /** + * Gets the Compiler instance. + * + * @return Twig_CompilerInterface A Twig_CompilerInterface instance + */ + public function getCompiler() + { + if (null === $this->compiler) { + $this->compiler = new Twig_Compiler($this); + } + + return $this->compiler; + } + + /** + * Sets the Compiler instance. + * + * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance + */ + public function setCompiler(Twig_CompilerInterface $compiler) + { + $this->compiler = $compiler; + } + + /** + * Compiles a Node. + * + * @param Twig_NodeInterface $node A Twig_NodeInterface instance + * + * @return string The compiled PHP source code + */ + public function compile(Twig_NodeInterface $node) + { + return $this->getCompiler()->compile($node)->getSource(); + } + + /** + * Compiles a template source code. + * + * @param string $source The template source code + * @param string $name The template name + * + * @return string The compiled PHP source code + */ + public function compileSource($source, $name = null) + { + return $this->compile($this->parse($this->tokenize($source, $name))); + } + + /** + * Sets the Loader instance. + * + * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance + */ + public function setLoader(Twig_LoaderInterface $loader) + { + $this->loader = $loader; + } + + /** + * Gets the Loader instance. + * + * @return Twig_LoaderInterface A Twig_LoaderInterface instance + */ + public function getLoader() + { + return $this->loader; + } + + /** + * Sets the default template charset. + * + * @param string $charset The default charset + */ + public function setCharset($charset) + { + $this->charset = $charset; + } + + /** + * Gets the default template charset. + * + * @return string The default charset + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Initializes the runtime environment. + */ + public function initRuntime() + { + $this->runtimeInitialized = true; + + foreach ($this->getExtensions() as $extension) { + $extension->initRuntime($this); + } + } + + /** + * Returns true if the given extension is registered. + * + * @param string $name The extension name + * + * @return Boolean Whether the extension is registered or not + */ + public function hasExtension($name) + { + return isset($this->extensions[$name]); + } + + /** + * Gets an extension by name. + * + * @param string $name The extension name + * + * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance + */ + public function getExtension($name) + { + if (!isset($this->extensions[$name])) { + throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name)); + } + + return $this->extensions[$name]; + } + + /** + * Registers an extension. + * + * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance + */ + public function addExtension(Twig_ExtensionInterface $extension) + { + $this->extensions[$extension->getName()] = $extension; + } + + /** + * Removes an extension by name. + * + * @param string $name The extension name + */ + public function removeExtension($name) + { + unset($this->extensions[$name]); + } + + /** + * Registers an array of extensions. + * + * @param array $extensions An array of extensions + */ + public function setExtensions(array $extensions) + { + foreach ($extensions as $extension) { + $this->addExtension($extension); + } + } + + /** + * Returns all registered extensions. + * + * @return array An array of extensions + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Registers a Token Parser. + * + * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance + */ + public function addTokenParser(Twig_TokenParserInterface $parser) + { + if (null === $this->parsers) { + $this->getTokenParsers(); + } + + $this->parsers->addTokenParser($parser); + } + + /** + * Gets the registered Token Parsers. + * + * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances + */ + public function getTokenParsers() + { + if (null === $this->parsers) { + $this->parsers = new Twig_TokenParserBroker; + foreach ($this->getExtensions() as $extension) { + $parsers = $extension->getTokenParsers(); + foreach($parsers as $parser) { + if ($parser instanceof Twig_TokenParserInterface) { + $this->parsers->addTokenParser($parser); + } else if ($parser instanceof Twig_TokenParserBrokerInterface) { + $this->parsers->addTokenParserBroker($parser); + } else { + throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances'); + } + } + } + } + + return $this->parsers; + } + + /** + * Registers a Node Visitor. + * + * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance + */ + public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) + { + if (null === $this->visitors) { + $this->getNodeVisitors(); + } + + $this->visitors[] = $visitor; + } + + /** + * Gets the registered Node Visitors. + * + * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances + */ + public function getNodeVisitors() + { + if (null === $this->visitors) { + $this->visitors = array(); + foreach ($this->getExtensions() as $extension) { + $this->visitors = array_merge($this->visitors, $extension->getNodeVisitors()); + } + } + + return $this->visitors; + } + + /** + * Registers a Filter. + * + * @param string $name The filter name + * @param Twig_FilterInterface $visitor A Twig_FilterInterface instance + */ + public function addFilter($name, Twig_FilterInterface $filter) + { + if (null === $this->filters) { + $this->loadFilters(); + } + + $this->filters[$name] = $filter; + } + + /** + * Get a filter by name. + * + * Subclasses may override this method and load filters differently; + * so no list of filters is available. + * + * @param string $name The filter name + * + * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists + */ + public function getFilter($name) + { + if (null === $this->filters) { + $this->loadFilters(); + } + + if (isset($this->filters[$name])) { + return $this->filters[$name]; + } + + foreach ($this->filterCallbacks as $callback) { + if (false !== $filter = call_user_func($callback, $name)) { + return $filter; + } + } + + return false; + } + + public function registerUndefinedFilterCallback($callable) + { + $this->filterCallbacks[] = $callable; + } + + /** + * Gets the registered Filters. + * + * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances + */ + protected function loadFilters() + { + $this->filters = array(); + foreach ($this->getExtensions() as $extension) { + $this->filters = array_merge($this->filters, $extension->getFilters()); + } + } + + /** + * Registers a Test. + * + * @param string $name The test name + * @param Twig_TestInterface $visitor A Twig_TestInterface instance + */ + public function addTest($name, Twig_TestInterface $test) + { + if (null === $this->tests) { + $this->getTests(); + } + + $this->tests[$name] = $test; + } + + /** + * Gets the registered Tests. + * + * @return Twig_TestInterface[] An array of Twig_TestInterface instances + */ + public function getTests() + { + if (null === $this->tests) { + $this->tests = array(); + foreach ($this->getExtensions() as $extension) { + $this->tests = array_merge($this->tests, $extension->getTests()); + } + } + + return $this->tests; + } + + /** + * Registers a Function. + * + * @param string $name The function name + * @param Twig_FunctionInterface $visitor A Twig_FunctionInterface instance + */ + public function addFunction($name, Twig_FunctionInterface $function) + { + if (null === $this->functions) { + $this->loadFunctions(); + } + + $this->functions[$name] = $function; + } + + /** + * Get a function by name. + * + * Subclasses may override this method and load functions differently; + * so no list of functions is available. + * + * @param string $name function name + * + * @return Twig_Function|false A Twig_Function instance or false if the function does not exists + */ + public function getFunction($name) + { + if (null === $this->functions) { + $this->loadFunctions(); + } + + if (isset($this->functions[$name])) { + return $this->functions[$name]; + } + + foreach ($this->functionCallbacks as $callback) { + if (false !== $function = call_user_func($callback, $name)) { + return $function; + } + } + + return false; + } + + public function registerUndefinedFunctionCallback($callable) + { + $this->functionCallbacks[] = $callable; + } + + protected function loadFunctions() { + $this->functions = array(); + foreach ($this->getExtensions() as $extension) { + $this->functions = array_merge($this->functions, $extension->getFunctions()); + } + } + + /** + * Registers a Global. + * + * @param string $name The global name + * @param mixed $value The global value + */ + public function addGlobal($name, $value) + { + if (null === $this->globals) { + $this->getGlobals(); + } + + $this->globals[$name] = $value; + } + + /** + * Gets the registered Globals. + * + * @return array An array of globals + */ + public function getGlobals() + { + if (null === $this->globals) { + $this->globals = array(); + foreach ($this->getExtensions() as $extension) { + $this->globals = array_merge($this->globals, $extension->getGlobals()); + } + } + + return $this->globals; + } + + /** + * Gets the registered unary Operators. + * + * @return array An array of unary operators + */ + public function getUnaryOperators() + { + if (null === $this->unaryOperators) { + $this->initOperators(); + } + + return $this->unaryOperators; + } + + /** + * Gets the registered binary Operators. + * + * @return array An array of binary operators + */ + public function getBinaryOperators() + { + if (null === $this->binaryOperators) { + $this->initOperators(); + } + + return $this->binaryOperators; + } + + protected function initOperators() + { + $this->unaryOperators = array(); + $this->binaryOperators = array(); + foreach ($this->getExtensions() as $extension) { + $operators = $extension->getOperators(); + + if (!$operators) { + continue; + } + + if (2 !== count($operators)) { + throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension))); + } + + $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); + $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + } + } + + protected function writeCacheFile($file, $content) + { + if (!is_dir(dirname($file))) { + mkdir(dirname($file), 0777, true); + } + + $tmpFile = tempnam(dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content)) { + // rename does not work on Win32 before 5.2.6 + if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) { + chmod($file, 0644); + + return; + } + } + + throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file)); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Error.php b/lib/Twig-1.0.0-RC1/lib/Twig/Error.php new file mode 100755 index 0000000..ae6143c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Error.php @@ -0,0 +1,76 @@ + + */ +class Twig_Error extends Exception +{ + protected $lineno; + protected $filename; + protected $rawMessage; + + /** + * Constructor. + * + * @param string $message The error message + * @param integer $lineno The template line where the error occurred + * @param string $filename The template file name where the error occurred + */ + public function __construct($message, $lineno = -1, $filename = null) + { + $this->lineno = $lineno; + $this->filename = $filename; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message); + } + + /** + * Gets the filename where the error occurred. + * + * @return string The filename + */ + public function getFilename() + { + return $this->filename; + } + + /** + * Sets the filename where the error occurred. + * + * @param string $filename The filename + */ + public function setFilename($filename) + { + $this->filename = $filename; + + $this->updateRepr(); + } + + protected function updateRepr() + { + $this->message = $this->rawMessage; + + if (null !== $this->filename) { + $this->message .= sprintf(' in %s', $this->filename); + } + + if ($this->lineno >= 0) { + $this->message .= sprintf(' at line %d', $this->lineno); + } + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Error/Loader.php b/lib/Twig-1.0.0-RC1/lib/Twig/Error/Loader.php new file mode 100755 index 0000000..facaf2d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Error/Loader.php @@ -0,0 +1,20 @@ + + */ +class Twig_Error_Loader extends Twig_Error +{ +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Error/Runtime.php b/lib/Twig-1.0.0-RC1/lib/Twig/Error/Runtime.php new file mode 100755 index 0000000..262f8f0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Error/Runtime.php @@ -0,0 +1,21 @@ + + */ +class Twig_Error_Runtime extends Twig_Error +{ +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Error/Syntax.php b/lib/Twig-1.0.0-RC1/lib/Twig/Error/Syntax.php new file mode 100755 index 0000000..730c75f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Error/Syntax.php @@ -0,0 +1,21 @@ + + */ +class Twig_Error_Syntax extends Twig_Error +{ +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/ExpressionParser.php b/lib/Twig-1.0.0-RC1/lib/Twig/ExpressionParser.php new file mode 100755 index 0000000..aa8be80 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/ExpressionParser.php @@ -0,0 +1,374 @@ + + */ +class Twig_ExpressionParser +{ + const OPERATOR_LEFT = 1; + const OPERATOR_RIGHT = 2; + + protected $parser; + protected $unaryOperators; + protected $binaryOperators; + + public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators) + { + $this->parser = $parser; + $this->unaryOperators = $unaryOperators; + $this->binaryOperators = $binaryOperators; + } + + public function parseExpression($precedence = 0) + { + $expr = $this->getPrimary(); + $token = $this->parser->getCurrentToken(); + while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + + if (isset($op['callable'])) { + $expr = call_user_func($op['callable'], $this->parser, $expr); + } else { + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $class = $op['class']; + $expr = new $class($expr, $expr1, $token->getLine()); + } + + $token = $this->parser->getCurrentToken(); + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + protected function getPrimary() + { + $token = $this->parser->getCurrentToken(); + + if ($this->isUnary($token)) { + $operator = $this->unaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + $expr = $this->parseExpression($operator['precedence']); + $class = $operator['class']; + + return $this->parsePostfixExpression(new $class($expr, $token->getLine())); + } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $this->parser->getStream()->next(); + $expr = $this->parseExpression(); + $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + protected function parseConditionalExpression($expr) + { + while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) { + $this->parser->getStream()->next(); + $expr2 = $this->parseExpression(); + $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'The ternary operator must have a default value'); + $expr3 = $this->parseExpression(); + + $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); + } + + return $expr; + } + + protected function isUnary(Twig_Token $token) + { + return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); + } + + protected function isBinary(Twig_Token $token) + { + return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); + } + + public function parsePrimaryExpression() + { + $token = $this->parser->getCurrentToken(); + switch ($token->getType()) { + case Twig_Token::NAME_TYPE: + $this->parser->getStream()->next(); + switch ($token->getValue()) { + case 'true': + $node = new Twig_Node_Expression_Constant(true, $token->getLine()); + break; + + case 'false': + $node = new Twig_Node_Expression_Constant(false, $token->getLine()); + break; + + case 'none': + $node = new Twig_Node_Expression_Constant(null, $token->getLine()); + break; + + default: + $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); + } + break; + + case Twig_Token::NUMBER_TYPE: + case Twig_Token::STRING_TYPE: + $this->parser->getStream()->next(); + $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); + break; + + default: + if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseHashExpression(); + } else { + throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseArrayExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); + $elements = array(); + while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { + if (!empty($elements)) { + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + + $elements[] = $this->parseExpression(); + } + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); + + return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); + } + + public function parseHashExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); + $elements = array(); + while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { + if (!empty($elements)) { + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + + if (!$stream->test(Twig_Token::STRING_TYPE) && !$stream->test(Twig_Token::NUMBER_TYPE)) { + $current = $stream->getCurrent(); + throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string or a number (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine()); + } + + $key = $stream->next()->getValue(); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); + $elements[$key] = $this->parseExpression(); + } + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); + + return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); + } + + public function parsePostfixExpression($node) + { + $firstPass = true; + while (true) { + $token = $this->parser->getCurrentToken(); + if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) { + if ('.' == $token->getValue() || '[' == $token->getValue()) { + $node = $this->parseSubscriptExpression($node); + } elseif ('|' == $token->getValue()) { + $node = $this->parseFilterExpression($node); + } elseif ($firstPass && $node instanceof Twig_Node_Expression_Name && '(' == $token->getValue()) { + $node = $this->getFunctionNode($node); + } else { + break; + } + } else { + break; + } + + $firstPass = false; + } + + return $node; + } + + public function getFunctionNode($node) + { + $args = $this->parseArguments(); + + if ('parent' === $node->getAttribute('name')) { + if (!count($this->parser->getBlockStack())) { + throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $token->getLine()); + } + + if (!$this->parser->getParent()) { + throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend another one is forbidden', $token->getLine()); + } + + return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $node->getLine()); + } + + if ('block' === $node->getAttribute('name')) { + return new Twig_Node_Expression_BlockReference($args->getNode(0), $node->getLine()); + } + + if (null !== $alias = $this->parser->getImportedFunction($node->getAttribute('name'))) { + return new Twig_Node_Expression_GetAttr($alias['node'], new Twig_Node_Expression_Constant($alias['name'], $node->getLine()), $args, $node->getLine(), Twig_Node_Expression_GetAttr::TYPE_METHOD); + } + + return new Twig_Node_Expression_Function($node, $args, $node->getLine()); + } + + public function parseSubscriptExpression($node) + { + $token = $this->parser->getStream()->next(); + $lineno = $token->getLine(); + $arguments = new Twig_Node(); + $type = Twig_Node_Expression_GetAttr::TYPE_ANY; + if ($token->getValue() == '.') { + $token = $this->parser->getStream()->next(); + if ( + $token->getType() == Twig_Token::NAME_TYPE + || + $token->getType() == Twig_Token::NUMBER_TYPE + || + ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) + ) { + $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); + + if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $type = Twig_Node_Expression_GetAttr::TYPE_METHOD; + $arguments = $this->parseArguments(); + } else { + $arguments = new Twig_Node(); + } + } else { + throw new Twig_Error_Syntax('Expected name or number', $lineno); + } + } else { + $type = Twig_Node_Expression_GetAttr::TYPE_ARRAY; + + $arg = $this->parseExpression(); + $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ']'); + } + + return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno); + } + + public function parseFilterExpression($node) + { + $this->parser->getStream()->next(); + + return $this->parseFilterExpressionRaw($node); + } + + public function parseFilterExpressionRaw($node, $tag = null) + { + while (true) { + $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE); + + $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); + if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $arguments = new Twig_Node(); + } else { + $arguments = $this->parseArguments(); + } + + $node = new Twig_Node_Expression_Filter($node, $name, $arguments, $token->getLine(), $tag); + + if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) { + break; + } + + $this->parser->getStream()->next(); + } + + return $node; + } + + public function parseArguments() + { + $args = array(); + $stream = $this->parser->getStream(); + + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis'); + while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) { + if (!empty($args)) { + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + } + $args[] = $this->parseExpression(); + } + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Twig_Node($args); + } + + public function parseAssignmentExpression() + { + $targets = array(); + while (true) { + $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); + if (in_array($token->getValue(), array('true', 'false', 'none'))) { + throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine()); + } + $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine()); + + if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { + break; + } + $this->parser->getStream()->next(); + } + + return new Twig_Node($targets); + } + + public function parseMultitargetExpression() + { + $targets = array(); + while (true) { + $targets[] = $this->parseExpression(); + if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { + break; + } + $this->parser->getStream()->next(); + } + + return new Twig_Node($targets); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Extension.php b/lib/Twig-1.0.0-RC1/lib/Twig/Extension.php new file mode 100755 index 0000000..ac289cb --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Extension.php @@ -0,0 +1,93 @@ + new Twig_Filter_Function('twig_date_format_filter'), + 'format' => new Twig_Filter_Function('sprintf'), + 'replace' => new Twig_Filter_Function('twig_strtr'), + + // encoding + 'url_encode' => new Twig_Filter_Function('twig_urlencode_filter'), + 'json_encode' => new Twig_Filter_Function('json_encode'), + + // string filters + 'title' => new Twig_Filter_Function('twig_title_string_filter', array('needs_environment' => true)), + 'capitalize' => new Twig_Filter_Function('twig_capitalize_string_filter', array('needs_environment' => true)), + 'upper' => new Twig_Filter_Function('strtoupper'), + 'lower' => new Twig_Filter_Function('strtolower'), + 'striptags' => new Twig_Filter_Function('strip_tags'), + + // array helpers + 'join' => new Twig_Filter_Function('twig_join_filter'), + 'reverse' => new Twig_Filter_Function('twig_reverse_filter'), + 'length' => new Twig_Filter_Function('twig_length_filter', array('needs_environment' => true)), + 'sort' => new Twig_Filter_Function('twig_sort_filter'), + 'merge' => new Twig_Filter_Function('twig_array_merge'), + + // iteration and runtime + 'default' => new Twig_Filter_Function('twig_default_filter'), + 'keys' => new Twig_Filter_Function('twig_get_array_keys_filter'), + + // escaping + 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), + 'e' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), + ); + + if (function_exists('mb_get_info')) { + $filters['upper'] = new Twig_Filter_Function('twig_upper_filter', array('needs_environment' => true)); + $filters['lower'] = new Twig_Filter_Function('twig_lower_filter', array('needs_environment' => true)); + } + + return $filters; + } + + /** + * Returns a list of global functions to add to the existing list. + * + * @return array An array of global functions + */ + public function getFunctions() + { + return array( + 'range' => new Twig_Function_Method($this, 'getRange'), + 'constant' => new Twig_Function_Method($this, 'getConstant'), + 'cycle' => new Twig_Function_Method($this, 'getCycle'), + ); + } + + public function getRange($start, $end, $step = 1) + { + return range($start, $end, $step); + } + + public function getConstant($value) + { + return constant($value); + } + + public function getCycle($values, $i) + { + if (!is_array($values) && !$values instanceof ArrayAccess) { + return $values; + } + + return $values[$i % count($values)]; + } + + /** + * Returns a list of filters to add to the existing list. + * + * @return array An array of filters + */ + public function getTests() + { + return array( + 'even' => new Twig_Test_Function('twig_test_even'), + 'odd' => new Twig_Test_Function('twig_test_odd'), + 'defined' => new Twig_Test_Function('twig_test_defined'), + 'sameas' => new Twig_Test_Function('twig_test_sameas'), + 'none' => new Twig_Test_Function('twig_test_none'), + 'divisibleby' => new Twig_Test_Function('twig_test_divisibleby'), + 'constant' => new Twig_Test_Function('twig_test_constant'), + 'empty' => new Twig_Test_Function('twig_test_empty'), + ); + } + + /** + * Returns a list of operators to add to the existing list. + * + * @return array An array of operators + */ + public function getOperators() + { + return array( + array( + 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + '-' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Neg'), + '+' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Pos'), + ), + array( + 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '..' => array('precedence' => 110, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), + ), + ); + } + + public function parseNotTestExpression($parser, $node) + { + return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine()); + } + + public function parseTestExpression($parser, $node) + { + $stream = $parser->getStream(); + $name = $stream->expect(Twig_Token::NAME_TYPE); + $arguments = null; + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $arguments = $parser->getExpressionParser()->parseArguments($node); + } + + return new Twig_Node_Expression_Test($node, $name->getValue(), $arguments, $parser->getCurrentToken()->getLine()); + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'core'; + } +} + +function twig_date_format_filter($date, $format = 'F j, Y H:i') +{ + if (!$date instanceof DateTime) { + $date = new DateTime((ctype_digit($date) ? '@' : '').$date); + } + + return $date->format($format); +} + +function twig_urlencode_filter($url, $raw = false) +{ + if ($raw) { + return rawurlencode($url); + } + + return urlencode($url); +} + +function twig_array_merge($arr1, $arr2) +{ + if (!is_array($arr1) || !is_array($arr2)) { + throw new Twig_Error_Runtime('The merge filter only work with arrays or hashes.'); + } + + return array_merge($arr1, $arr2); +} + +function twig_join_filter($value, $glue = '') +{ + return implode($glue, (array) $value); +} + +function twig_default_filter($value, $default = '') +{ + return twig_test_empty($value) ? $default : $value; +} + +function twig_get_array_keys_filter($array) +{ + if (is_object($array) && $array instanceof Traversable) { + return array_keys(iterator_to_array($array)); + } + + if (!is_array($array)) { + return array(); + } + + return array_keys($array); +} + +function twig_reverse_filter($array) +{ + if (is_object($array) && $array instanceof Traversable) { + return array_reverse(iterator_to_array($array)); + } + + if (!is_array($array)) { + return array(); + } + + return array_reverse($array); +} + +function twig_sort_filter($array) +{ + asort($array); + + return $array; +} + +function twig_in_filter($value, $compare) +{ + if (is_array($compare)) { + return in_array($value, $compare); + } elseif (is_string($compare)) { + return false !== strpos($compare, (string) $value); + } elseif (is_object($compare) && $compare instanceof Traversable) { + return in_array($value, iterator_to_array($compare, false)); + } + + return false; +} + +function twig_strtr($pattern, $replacements) +{ + return str_replace(array_keys($replacements), array_values($replacements), $pattern); +} + +/* + * Each type specifies a way for applying a transformation to a string + * The purpose is for the string to be "escaped" so it is suitable for + * the format it is being displayed in. + * + * For example, the string: "It's required that you enter a username & password.\n" + * If this were to be displayed as HTML it would be sensible to turn the + * ampersand into '&' and the apostrophe into '&aps;'. However if it were + * going to be used as a string in JavaScript to be displayed in an alert box + * it would be right to leave the string as-is, but c-escape the apostrophe and + * the new line. + */ +function twig_escape_filter(Twig_Environment $env, $string, $type = 'html') +{ + if (is_object($string) && $string instanceof Twig_Markup) { + return $string; + } + + if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) { + return $string; + } + + switch ($type) { + case 'js': + // escape all non-alphanumeric characters + // into their \xHH or \uHHHH representations + $charset = $env->getCharset(); + + if ('UTF-8' != $charset) { + $string = _twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (null === $string = preg_replace_callback('#[^\p{L}\p{N} ]#u', '_twig_escape_js_callback', $string)) { + throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); + } + + if ('UTF-8' != $charset) { + $string = _twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + + case 'html': + return htmlspecialchars($string, ENT_QUOTES, $env->getCharset()); + + default: + throw new Twig_Error_Runtime(sprintf('Invalid escape type "%s".', $type)); + } +} + +function twig_escape_filter_is_safe(Twig_Node $filterArgs) +{ + foreach ($filterArgs as $arg) { + if ($arg instanceof Twig_Node_Expression_Constant) { + return array($arg->getAttribute('value')); + } else { + return array(); + } + + break; + } + + return array('html'); +} + +if (function_exists('iconv')) { + function _twig_convert_encoding($string, $to, $from) + { + return iconv($from, $to, $string); + } +} elseif (function_exists('mb_convert_encoding')) { + function _twig_convert_encoding($string, $to, $from) + { + return mb_convert_encoding($string, $to, $from); + } +} else { + function _twig_convert_encoding($string, $to, $from) + { + throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); + } +} + +function _twig_escape_js_callback($matches) +{ + $char = $matches[0]; + + // \xHH + if (!isset($char[1])) { + return '\\x'.substr('00'.bin2hex($char), -2); + } + + // \uHHHH + $char = _twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); + + return '\\u'.substr('0000'.bin2hex($char), -4); +} + +// add multibyte extensions if possible +if (function_exists('mb_get_info')) { + function twig_length_filter(Twig_Environment $env, $thing) + { + return is_string($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing); + } + + function twig_upper_filter(Twig_Environment $env, $string) + { + if (null !== ($charset = $env->getCharset())) { + return mb_strtoupper($string, $charset); + } + + return strtoupper($string); + } + + function twig_lower_filter(Twig_Environment $env, $string) + { + if (null !== ($charset = $env->getCharset())) { + return mb_strtolower($string, $charset); + } + + return strtolower($string); + } + + function twig_title_string_filter(Twig_Environment $env, $string) + { + if (null !== ($charset = $env->getCharset())) { + return mb_convert_case($string, MB_CASE_TITLE, $charset); + } + + return ucwords(strtolower($string)); + } + + function twig_capitalize_string_filter(Twig_Environment $env, $string) + { + if (null !== ($charset = $env->getCharset())) { + return mb_strtoupper(mb_substr($string, 0, 1, $charset)). + mb_strtolower(mb_substr($string, 1, mb_strlen($string), $charset), $charset); + } + + return ucfirst(strtolower($string)); + } +} +// and byte fallback +else +{ + function twig_length_filter(Twig_Environment $env, $thing) + { + return is_string($thing) ? strlen($thing) : count($thing); + } + + function twig_title_string_filter(Twig_Environment $env, $string) + { + return ucwords(strtolower($string)); + } + + function twig_capitalize_string_filter(Twig_Environment $env, $string) + { + return ucfirst(strtolower($string)); + } +} + +function twig_ensure_traversable($seq) +{ + if (is_array($seq) || (is_object($seq) && $seq instanceof Traversable)) { + return $seq; + } else { + return array(); + } +} + +function twig_test_sameas($value, $test) +{ + return $value === $test; +} + +function twig_test_none($value) +{ + return null === $value; +} + +function twig_test_divisibleby($value, $num) +{ + return 0 == $value % $num; +} + +function twig_test_even($value) +{ + return $value % 2 == 0; +} + +function twig_test_odd($value) +{ + return $value % 2 == 1; +} + +function twig_test_constant($value, $constant) +{ + return constant($constant) === $value; +} + +function twig_test_defined($name, $context) +{ + return array_key_exists($name, $context); +} + +function twig_test_empty($value) +{ + return null === $value || false === $value || '' === (string) $value; +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Escaper.php b/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Escaper.php new file mode 100755 index 0000000..62c5a1a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Escaper.php @@ -0,0 +1,73 @@ +autoescape = $autoescape; + } + + /** + * Returns the token parser instances to add to the existing list. + * + * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances + */ + public function getTokenParsers() + { + return array(new Twig_TokenParser_AutoEscape()); + } + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return array An array of Twig_NodeVisitorInterface instances + */ + public function getNodeVisitors() + { + return array(new Twig_NodeVisitor_Escaper()); + } + + /** + * Returns a list of filters to add to the existing list. + * + * @return array An array of filters + */ + public function getFilters() + { + return array( + 'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_safe' => array('all'))), + ); + } + + public function isGlobal() + { + return $this->autoescape; + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'escaper'; + } +} + +// tells the escaper node visitor that the string is safe +function twig_raw_filter($string) +{ + return $string; +} + diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Optimizer.php b/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Optimizer.php new file mode 100755 index 0000000..013fcb6 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Optimizer.php @@ -0,0 +1,35 @@ +optimizers = $optimizers; + } + + /** + * {@inheritdoc} + */ + public function getNodeVisitors() + { + return array(new Twig_NodeVisitor_Optimizer($this->optimizers)); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'optimizer'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Sandbox.php b/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Sandbox.php new file mode 100755 index 0000000..c5cbbbf --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Extension/Sandbox.php @@ -0,0 +1,103 @@ +policy = $policy; + $this->sandboxedGlobally = $sandboxed; + } + + /** + * Returns the token parser instances to add to the existing list. + * + * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances + */ + public function getTokenParsers() + { + return array(new Twig_TokenParser_Sandbox()); + } + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return array An array of Twig_NodeVisitorInterface instances + */ + public function getNodeVisitors() + { + return array(new Twig_NodeVisitor_Sandbox()); + } + + public function enableSandbox() + { + $this->sandboxed = true; + } + + public function disableSandbox() + { + $this->sandboxed = false; + } + + public function isSandboxed() + { + return $this->sandboxedGlobally || $this->sandboxed; + } + + public function isSandboxedGlobally() + { + return $this->sandboxedGlobally; + } + + public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy) + { + $this->policy = $policy; + } + + public function getSecurityPolicy() + { + return $this->policy; + } + + public function checkSecurity($tags, $filters, $functions) + { + if ($this->isSandboxed()) { + $this->policy->checkSecurity($tags, $filters, $functions); + } + } + + public function checkMethodAllowed($obj, $method) + { + if ($this->isSandboxed()) { + $this->policy->checkMethodAllowed($obj, $method); + } + } + + public function checkPropertyAllowed($obj, $method) + { + if ($this->isSandboxed()) { + $this->policy->checkPropertyAllowed($obj, $method); + } + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'sandbox'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/ExtensionInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/ExtensionInterface.php new file mode 100755 index 0000000..af8d93e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/ExtensionInterface.php @@ -0,0 +1,84 @@ + + */ +interface Twig_ExtensionInterface +{ + /** + * Initializes the runtime environment. + * + * This is where you can load some file that contains filter functions for instance. + * + * @param Twig_Environment $environment The current Twig_Environment instance + */ + function initRuntime(Twig_Environment $environment); + + /** + * Returns the token parser instances to add to the existing list. + * + * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances + */ + function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return array An array of Twig_NodeVisitorInterface instances + */ + function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return array An array of filters + */ + function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return array An array of tests + */ + function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return array An array of functions + */ + function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array An array of operators + */ + function getOperators(); + + /** + * Returns a list of global functions to add to the existing list. + * + * @return array An array of global functions + */ + function getGlobals(); + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + function getName(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Filter.php b/lib/Twig-1.0.0-RC1/lib/Twig/Filter.php new file mode 100755 index 0000000..3484df2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Filter.php @@ -0,0 +1,58 @@ + + */ +abstract class Twig_Filter implements Twig_FilterInterface +{ + protected $options; + + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'needs_environment' => false, + 'needs_context' => false, + 'pre_escape' => null, + ), $options); + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Twig_Node $filterArgs) + { + if (isset($this->options['is_safe'])) { + return $this->options['is_safe']; + } + + if (isset($this->options['is_safe_callback'])) { + return call_user_func($this->options['is_safe_callback'], $filterArgs); + } + + return array(); + } + + public function getPreEscape() + { + return $this->options['pre_escape']; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Filter/Function.php b/lib/Twig-1.0.0-RC1/lib/Twig/Filter/Function.php new file mode 100755 index 0000000..9950332 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Filter/Function.php @@ -0,0 +1,33 @@ + + */ +class Twig_Filter_Function extends Twig_Filter +{ + protected $function; + + public function __construct($function, array $options = array()) + { + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Filter/Method.php b/lib/Twig-1.0.0-RC1/lib/Twig/Filter/Method.php new file mode 100755 index 0000000..aa7f041 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Filter/Method.php @@ -0,0 +1,34 @@ + + */ +class Twig_Filter_Method extends Twig_Filter +{ + protected $extension, $method; + + public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) + { + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/FilterInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/FilterInterface.php new file mode 100755 index 0000000..eb6ac57 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/FilterInterface.php @@ -0,0 +1,32 @@ + + */ +interface Twig_FilterInterface +{ + /** + * Compiles a filter. + * + * @return string The PHP code for the filter + */ + function compile(); + + function needsEnvironment(); + + function getSafe(Twig_Node $filterArgs); + + function getPreEscape(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Function.php b/lib/Twig-1.0.0-RC1/lib/Twig/Function.php new file mode 100755 index 0000000..99e9b77 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Function.php @@ -0,0 +1,52 @@ + + */ +abstract class Twig_Function implements Twig_FunctionInterface +{ + protected $options; + + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'needs_environment' => false, + 'needs_context' => false, + ), $options); + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Twig_Node $functionArgs) + { + if (isset($this->options['is_safe'])) { + return $this->options['is_safe']; + } + + if (isset($this->options['is_safe_callback'])) { + return call_user_func($this->options['is_safe_callback'], $functionArgs); + } + + return array(); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Function/Function.php b/lib/Twig-1.0.0-RC1/lib/Twig/Function/Function.php new file mode 100755 index 0000000..3237d8c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Function/Function.php @@ -0,0 +1,34 @@ + + */ +class Twig_Function_Function extends Twig_Function +{ + protected $function; + + public function __construct($function, array $options = array()) + { + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Function/Method.php b/lib/Twig-1.0.0-RC1/lib/Twig/Function/Method.php new file mode 100755 index 0000000..7328566 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Function/Method.php @@ -0,0 +1,35 @@ + + */ +class Twig_Function_Method extends Twig_Function +{ + protected $extension, $method; + + public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) + { + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/FunctionInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/FunctionInterface.php new file mode 100755 index 0000000..01d3566 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/FunctionInterface.php @@ -0,0 +1,31 @@ + + */ +interface Twig_FunctionInterface +{ + /** + * Compiles a function. + * + * @return string The PHP code for the function + */ + function compile(); + + function needsEnvironment(); + + function getSafe(Twig_Node $filterArgs); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Lexer.php b/lib/Twig-1.0.0-RC1/lib/Twig/Lexer.php new file mode 100755 index 0000000..3dfeba6 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Lexer.php @@ -0,0 +1,306 @@ + + */ +class Twig_Lexer implements Twig_LexerInterface +{ + protected $tokens; + protected $code; + protected $cursor; + protected $lineno; + protected $end; + protected $state; + protected $brackets; + + protected $env; + protected $filename; + protected $options; + protected $operatorRegex; + + const STATE_DATA = 0; + const STATE_BLOCK = 1; + const STATE_VAR = 2; + + const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A'; + const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; + const REGEX_STRING = '/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + const PUNCTUATION = '()[]{}?:.,|'; + + public function __construct(Twig_Environment $env, array $options = array()) + { + $this->env = $env; + + $this->options = array_merge(array( + 'tag_comment' => array('{#', '#}'), + 'tag_block' => array('{%', '%}'), + 'tag_variable' => array('{{', '}}'), + ), $options); + } + + /** + * Tokenizes a source code. + * + * @param string $code The source code + * @param string $filename A unique identifier for the source code + * + * @return Twig_TokenStream A token stream instance + */ + public function tokenize($code, $filename = null) + { + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + $this->code = str_replace(array("\r\n", "\r"), "\n", $code); + $this->filename = $filename; + $this->cursor = 0; + $this->lineno = 1; + $this->end = strlen($this->code); + $this->tokens = array(); + $this->state = self::STATE_DATA; + $this->brackets = array(); + + while ($this->cursor < $this->end) { + // dispatch to the lexing functions depending + // on the current state + switch ($this->state) { + case self::STATE_DATA: + $this->lexData(); + break; + + case self::STATE_BLOCK: + $this->lexBlock(); + break; + + case self::STATE_VAR: + $this->lexVar(); + break; + } + } + + $this->pushToken(Twig_Token::EOF_TYPE); + + if (!empty($this->brackets)) { + list($expect, $lineno) = array_pop($this->brackets); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return new Twig_TokenStream($this->tokens, $this->filename); + } + + protected function lexData() + { + $pos = $this->end; + if (false !== ($tmpPos = strpos($this->code, $this->options['tag_comment'][0], $this->cursor)) && $tmpPos < $pos) { + $pos = $tmpPos; + $token = $this->options['tag_comment'][0]; + } + if (false !== ($tmpPos = strpos($this->code, $this->options['tag_variable'][0], $this->cursor)) && $tmpPos < $pos) { + $pos = $tmpPos; + $token = $this->options['tag_variable'][0]; + } + if (false !== ($tmpPos = strpos($this->code, $this->options['tag_block'][0], $this->cursor)) && $tmpPos < $pos) { + $pos = $tmpPos; + $token = $this->options['tag_block'][0]; + } + + // if no matches are left we return the rest of the template as simple text token + if ($pos === $this->end) { + $this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor)); + $this->cursor = $this->end; + return; + } + + // push the template text first + $text = substr($this->code, $this->cursor, $pos - $this->cursor); + if (!empty($text)) { + $this->pushToken(Twig_Token::TEXT_TYPE, $text); + } + $this->moveCursor($text.$token); + + switch ($token) { + case $this->options['tag_comment'][0]: + if (false === $pos = strpos($this->code, $this->options['tag_comment'][1], $this->cursor)) { + throw new Twig_Error_Syntax('unclosed comment', $this->lineno, $this->filename); + } + + $this->moveCursor(substr($this->code, $this->cursor, $pos - $this->cursor) . $this->options['tag_comment'][1]); + + // mimicks the behavior of PHP by removing the newline that follows instructions if present + if ("\n" === substr($this->code, $this->cursor, 1)) { + ++$this->cursor; + ++$this->lineno; + } + + break; + + case $this->options['tag_block'][0]: + // raw data? + if (preg_match('/\s*raw\s*'.preg_quote($this->options['tag_block'][1], '/').'(.*?)'.preg_quote($this->options['tag_block'][0], '/').'\s*endraw\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::TEXT_TYPE, $match[1]); + $this->moveCursor($match[0]); + $this->state = self::STATE_DATA; + } else { + $this->pushToken(Twig_Token::BLOCK_START_TYPE); + $this->state = self::STATE_BLOCK; + } + break; + + case $this->options['tag_variable'][0]: + $this->pushToken(Twig_Token::VAR_START_TYPE); + $this->state = self::STATE_VAR; + break; + } + } + + protected function lexBlock() + { + if (empty($this->brackets) && preg_match('/\s*'.preg_quote($this->options['tag_block'][1], '/').'/A', $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::BLOCK_END_TYPE); + $this->moveCursor($match[0]); + $this->state = self::STATE_DATA; + + // mimicks the behavior of PHP by removing the newline that follows instructions if present + if ("\n" === substr($this->code, $this->cursor, 1)) { + ++$this->cursor; + ++$this->lineno; + } + } + else { + $this->lexExpression(); + } + } + + protected function lexVar() + { + if (empty($this->brackets) && preg_match('/\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::VAR_END_TYPE); + $this->moveCursor($match[0]); + $this->state = self::STATE_DATA; + } + else { + $this->lexExpression(); + } + } + + protected function lexExpression() + { + // whitespace + if (preg_match('/\s+/A', $this->code, $match, null, $this->cursor)) { + $this->moveCursor($match[0]); + + if ($this->cursor >= $this->end) { + throw new Twig_Error_Syntax('Unexpected end of file: Unclosed ' . ($this->state === self::STATE_BLOCK ? 'block' : 'variable')); + } + } + + // operators + if (preg_match($this->getOperatorRegex(), $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]); + $this->moveCursor($match[0]); + } + // names + elseif (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::NAME_TYPE, $match[0]); + $this->moveCursor($match[0]); + } + // numbers + elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::NUMBER_TYPE, ctype_digit($match[0]) ? (int) $match[0] : (float) $match[0]); + $this->moveCursor($match[0]); + } + // punctuation + elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + // opening bracket + if (false !== strpos('([{', $this->code[$this->cursor])) { + $this->brackets[] = array($this->code[$this->cursor], $this->lineno); + } + // closing bracket + elseif (false !== strpos(')]}', $this->code[$this->cursor])) { + if (empty($this->brackets)) { + throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); + } + + list($expect, $lineno) = array_pop($this->brackets); + if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { + throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); + } + } + + $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); + ++$this->cursor; + } + // strings + elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); + $this->moveCursor($match[0]); + } + // unlexable + else { + throw new Twig_Error_Syntax(sprintf("Unexpected character '%s'", $this->code[$this->cursor]), $this->lineno, $this->filename); + } + } + + protected function pushToken($type, $value = '') { + // do not push empty text tokens + if (Twig_Token::TEXT_TYPE === $type && '' === $value) { + return; + } + + $this->tokens[] = new Twig_Token($type, $value, $this->lineno); + } + + protected function moveCursor($text) + { + $this->cursor += strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + protected function getOperatorRegex() + { + if (null !== $this->operatorRegex) { + return $this->operatorRegex; + } + + $operators = array_merge( + array('='), + array_keys($this->env->getUnaryOperators()), + array_keys($this->env->getBinaryOperators()) + ); + + $operators = array_combine($operators, array_map('strlen', $operators)); + arsort($operators); + + $regex = array(); + foreach ($operators as $operator => $length) { + // an operator that ends with a character must be followed by + // a whitespace or a parenthesis + if (ctype_alpha($operator[$length - 1])) { + $regex[] = preg_quote($operator, '/').'(?=[ ()])'; + } else { + $regex[] = preg_quote($operator, '/'); + } + } + + return $this->operatorRegex = '/'.implode('|', $regex).'/A'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/LexerInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/LexerInterface.php new file mode 100755 index 0000000..58fee31 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/LexerInterface.php @@ -0,0 +1,29 @@ + + */ +interface Twig_LexerInterface +{ + /** + * Tokenizes a source code. + * + * @param string $code The source code + * @param string $filename A unique identifier for the source code + * + * @return Twig_TokenStream A token stream instance + */ + function tokenize($code, $filename = null); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Loader/Array.php b/lib/Twig-1.0.0-RC1/lib/Twig/Loader/Array.php new file mode 100755 index 0000000..8c8798a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Loader/Array.php @@ -0,0 +1,84 @@ + + */ +class Twig_Loader_Array implements Twig_LoaderInterface +{ + protected $templates; + + /** + * Constructor. + * + * @param array $templates An array of templates (keys are the names, and values are the source code) + * + * @see Twig_Loader + */ + public function __construct(array $templates) + { + $this->templates = array(); + foreach ($templates as $name => $template) { + $this->templates[$name] = $template; + } + } + + /** + * Gets the source code of a template, given its name. + * + * @param string $name The name of the template to load + * + * @return string The template source code + */ + public function getSource($name) + { + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return $this->templates[$name]; + } + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name) + { + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return $this->templates[$name]; + } + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time) + { + return true; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Loader/Filesystem.php b/lib/Twig-1.0.0-RC1/lib/Twig/Loader/Filesystem.php new file mode 100755 index 0000000..d0f26d9 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Loader/Filesystem.php @@ -0,0 +1,133 @@ + + */ +class Twig_Loader_Filesystem implements Twig_LoaderInterface +{ + protected $paths; + protected $cache; + + /** + * Constructor. + * + * @param string|array $paths A path or an array of paths where to look for templates + */ + public function __construct($paths) + { + $this->setPaths($paths); + } + + /** + * Returns the paths to the templates. + * + * @return array The array of paths where to look for templates + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Sets the paths where templates are stored. + * + * @param string|array $paths A path or an array of paths where to look for templates + */ + public function setPaths($paths) + { + // invalidate the cache + $this->cache = array(); + + if (!is_array($paths)) { + $paths = array($paths); + } + + $this->paths = array(); + foreach ($paths as $path) { + if (!is_dir($path)) { + throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path)); + } + + $this->paths[] = $path; + } + } + + /** + * Gets the source code of a template, given its name. + * + * @param string $name The name of the template to load + * + * @return string The template source code + */ + public function getSource($name) + { + return file_get_contents($this->findTemplate($name)); + } + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name) + { + return $this->findTemplate($name); + } + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time) + { + return filemtime($this->findTemplate($name)) < $time; + } + + protected function findTemplate($name) + { + // normalize name + $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); + + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } + + $parts = explode('/', $name); + $level = 0; + foreach ($parts as $part) { + if ('..' === $part) { + --$level; + } elseif ('.' !== $part) { + ++$level; + } + + if ($level < 0) { + throw new Twig_Error_Loader('Looks like you try to load a template outside configured directories.'); + } + } + + foreach ($this->paths as $path) { + if (file_exists($path.'/'.$name) && !is_dir($path.'/'.$name)) { + return $this->cache[$name] = $path.'/'.$name; + } + } + + throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths))); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Loader/String.php b/lib/Twig-1.0.0-RC1/lib/Twig/Loader/String.php new file mode 100755 index 0000000..0593a72 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Loader/String.php @@ -0,0 +1,59 @@ + + */ +class Twig_Loader_String implements Twig_LoaderInterface +{ + /** + * Gets the source code of a template, given its name. + * + * @param string $name The name of the template to load + * + * @return string The template source code + */ + public function getSource($name) + { + return $name; + } + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name) + { + return $name; + } + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time) + { + return true; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/LoaderInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/LoaderInterface.php new file mode 100755 index 0000000..ef4adc4 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/LoaderInterface.php @@ -0,0 +1,45 @@ + + */ +interface Twig_LoaderInterface +{ + /** + * Gets the source code of a template, given its name. + * + * @param string $name The name of the template to load + * + * @return string The template source code + */ + function getSource($name); + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name The name of the template to load + * + * @return string The cache key + */ + function getCacheKey($name); + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + function isFresh($name, $time); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Markup.php b/lib/Twig-1.0.0-RC1/lib/Twig/Markup.php new file mode 100755 index 0000000..9a4d1df --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Markup.php @@ -0,0 +1,31 @@ + + */ +class Twig_Markup +{ + protected $content; + + public function __construct($content) + { + $this->content = (string) $content; + } + + public function __toString() + { + return $this->content; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node.php new file mode 100755 index 0000000..3274b59 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node.php @@ -0,0 +1,227 @@ + + */ +class Twig_Node implements Twig_NodeInterface, Countable, IteratorAggregate +{ + protected $nodes; + protected $attributes; + protected $lineno; + protected $tag; + + /** + * Constructor. + * + * The nodes are automatically made available as properties ($this->node). + * The attributes are automatically made available as array items ($this['name']). + * + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param integer $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null) + { + $this->nodes = $nodes; + $this->attributes = $attributes; + $this->lineno = $lineno; + $this->tag = $tag; + } + + public function __toString() + { + $attributes = array(); + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = array(get_class($this).'('.implode(', ', $attributes)); + + if (count($this->nodes)) { + foreach ($this->nodes as $name => $node) { + $len = strlen($name) + 4; + $noderepr = array(); + foreach (explode("\n", (string) $node) as $line) { + $noderepr[] = str_repeat(' ', $len).$line; + } + + $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + public function toXml($asDom = false) + { + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $dom->appendChild($xml = $dom->createElement('twig')); + + $xml->appendChild($node = $dom->createElement('node')); + $node->setAttribute('class', get_class($this)); + + foreach ($this->attributes as $name => $value) { + $node->appendChild($attribute = $dom->createElement('attribute')); + $attribute->setAttribute('name', $name); + $attribute->appendChild($dom->createTextNode($value)); + } + + foreach ($this->nodes as $name => $n) { + if (null === $n) { + continue; + } + + $child = $n->toXml(true)->getElementsByTagName('node')->item(0); + $child = $dom->importNode($child, true); + $child->setAttribute('name', $name); + + $node->appendChild($child); + } + + return $asDom ? $dom : $dom->saveXml(); + } + + public function compile(Twig_Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + public function getLine() + { + return $this->lineno; + } + + public function getNodeTag() + { + return $this->tag; + } + + /** + * Returns true if the attribute is defined. + * + * @param string The attribute name + * + * @return Boolean true if the attribute is defined, false otherwise + */ + public function hasAttribute($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * Gets an attribute. + * + * @param string The attribute name + * + * @return mixed The attribute value + */ + public function getAttribute($name) + { + if (!array_key_exists($name, $this->attributes)) { + throw new Twig_Error_Runtime(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this))); + } + + return $this->attributes[$name]; + } + + /** + * Sets an attribute. + * + * @param string The attribute name + * @param mixed The attribute value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * Removes an attribute. + * + * @param string The attribute name + */ + public function removeAttribute($name) + { + unset($this->attributes[$name]); + } + + /** + * Returns true if the node with the given identifier exists. + * + * @param string The node name + * + * @return Boolean true if the node with the given name exists, false otherwise + */ + public function hasNode($name) + { + return array_key_exists($name, $this->nodes); + } + + /** + * Gets a node by name. + * + * @param string The node name + * + * @return Twig_Node A Twig_Node instance + */ + public function getNode($name) + { + if (!array_key_exists($name, $this->nodes)) { + throw new Twig_Error_Runtime(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this))); + } + + return $this->nodes[$name]; + } + + /** + * Sets a node. + * + * @param string The node name + * @param Twig_Node A Twig_Node instance + */ + public function setNode($name, $node = null) + { + $this->nodes[$name] = $node; + } + + /** + * Removes a node by name. + * + * @param string The node name + */ + public function removeNode($name) + { + unset($this->nodes[$name]); + } + + public function count() + { + return count($this->nodes); + } + + public function getIterator() + { + return new ArrayIterator($this->nodes); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/AutoEscape.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/AutoEscape.php new file mode 100755 index 0000000..42816ac --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/AutoEscape.php @@ -0,0 +1,40 @@ + + */ +class Twig_Node_AutoEscape extends Twig_Node +{ + public function __construct($value, Twig_NodeInterface $body, $lineno, $tag = 'autoescape') + { + parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->subcompile($this->getNode('body')); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Block.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Block.php new file mode 100755 index 0000000..7bdb9b9 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Block.php @@ -0,0 +1,45 @@ + + */ +class Twig_Node_Block extends Twig_Node +{ + public function __construct($name, Twig_NodeInterface $body, $lineno, $tag = null) + { + parent::__construct(array('body' => $body), array('name' => $name), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("public function block_%s(\$context, array \$blocks = array())\n", $this->getAttribute('name')), "{\n") + ->indent() + ; + + $compiler + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/BlockReference.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/BlockReference.php new file mode 100755 index 0000000..42a62be --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/BlockReference.php @@ -0,0 +1,38 @@ + + */ +class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct(array(), array('name' => $name), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression.php new file mode 100755 index 0000000..2171831 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression.php @@ -0,0 +1,21 @@ + + */ +abstract class Twig_Node_Expression extends Twig_Node +{ +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Array.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Array.php new file mode 100755 index 0000000..2d86082 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Array.php @@ -0,0 +1,41 @@ +raw('array('); + $first = true; + foreach ($this->nodes as $name => $node) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler + ->repr($name) + ->raw(' => ') + ->subcompile($node) + ; + } + $compiler->raw(')'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/AssignName.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/AssignName.php new file mode 100755 index 0000000..67f1250 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/AssignName.php @@ -0,0 +1,24 @@ +raw(sprintf('$context[\'%s\']', $this->getAttribute('name'))); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary.php new file mode 100755 index 0000000..9dd5de2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary.php @@ -0,0 +1,40 @@ + $left, 'right' => $right), array(), $lineno); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('left')) + ->raw(' ') + ; + $this->operator($compiler); + $compiler + ->raw(' ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + abstract public function operator(Twig_Compiler $compiler); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Add.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Add.php new file mode 100755 index 0000000..0ef8e11 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Add.php @@ -0,0 +1,18 @@ +raw('+'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/And.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/And.php new file mode 100755 index 0000000..d5752eb --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/And.php @@ -0,0 +1,18 @@ +raw('&&'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Concat.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Concat.php new file mode 100755 index 0000000..f9a6462 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Concat.php @@ -0,0 +1,18 @@ +raw('.'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Div.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Div.php new file mode 100755 index 0000000..e0797a6 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Div.php @@ -0,0 +1,18 @@ +raw('/'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Equal.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Equal.php new file mode 100755 index 0000000..7b1236d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Equal.php @@ -0,0 +1,17 @@ +raw('=='); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/FloorDiv.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/FloorDiv.php new file mode 100755 index 0000000..e86b1ea --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/FloorDiv.php @@ -0,0 +1,29 @@ +raw('floor('); + parent::compile($compiler); + $compiler->raw(')'); + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('/'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Greater.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Greater.php new file mode 100755 index 0000000..a110bd9 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Greater.php @@ -0,0 +1,17 @@ +raw('>'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/GreaterEqual.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/GreaterEqual.php new file mode 100755 index 0000000..3754fed --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/GreaterEqual.php @@ -0,0 +1,17 @@ +raw('>='); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/In.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/In.php new file mode 100755 index 0000000..788f937 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/In.php @@ -0,0 +1,33 @@ +raw('twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('in'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Less.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Less.php new file mode 100755 index 0000000..45fd300 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Less.php @@ -0,0 +1,17 @@ +raw('<'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/LessEqual.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/LessEqual.php new file mode 100755 index 0000000..e38e257 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/LessEqual.php @@ -0,0 +1,17 @@ +raw('<='); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mod.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mod.php new file mode 100755 index 0000000..9924114 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mod.php @@ -0,0 +1,18 @@ +raw('%'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mul.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mul.php new file mode 100755 index 0000000..c91529c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Mul.php @@ -0,0 +1,18 @@ +raw('*'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotEqual.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotEqual.php new file mode 100755 index 0000000..26867ba --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotEqual.php @@ -0,0 +1,17 @@ +raw('!='); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotIn.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotIn.php new file mode 100755 index 0000000..f347b7b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/NotIn.php @@ -0,0 +1,33 @@ +raw('!twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('not in'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Or.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Or.php new file mode 100755 index 0000000..adba49c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Or.php @@ -0,0 +1,18 @@ +raw('||'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Power.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Power.php new file mode 100755 index 0000000..b2c5904 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Power.php @@ -0,0 +1,33 @@ +raw('pow(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('**'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Range.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Range.php new file mode 100755 index 0000000..bea4f2a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Range.php @@ -0,0 +1,33 @@ +raw('range(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('..'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Sub.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Sub.php new file mode 100755 index 0000000..d446399 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Binary/Sub.php @@ -0,0 +1,18 @@ +raw('-'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/BlockReference.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/BlockReference.php new file mode 100755 index 0000000..259967c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/BlockReference.php @@ -0,0 +1,39 @@ + + */ +class Twig_Node_Expression_BlockReference extends Twig_Node_Expression +{ + public function __construct(Twig_NodeInterface $name, $lineno, $tag = null) + { + parent::__construct(array('name' => $name), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw("\$this->renderBlock(") + ->subcompile($this->getNode('name')) + ->raw(", \$context, \$blocks)") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Conditional.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Conditional.php new file mode 100755 index 0000000..cefb9a2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Conditional.php @@ -0,0 +1,31 @@ + $expr1, 'expr2' => $expr2, 'expr3' => $expr3), array(), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw(')') + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Constant.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Constant.php new file mode 100755 index 0000000..a91dc69 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Constant.php @@ -0,0 +1,23 @@ + $value), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->repr($this->getAttribute('value')); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/ExtensionReference.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/ExtensionReference.php new file mode 100755 index 0000000..ba5d58e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/ExtensionReference.php @@ -0,0 +1,34 @@ + + */ +class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct(array(), array('name' => $name), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name'))); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Filter.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Filter.php new file mode 100755 index 0000000..091606a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Filter.php @@ -0,0 +1,72 @@ + $node, 'filter' => $filterName, 'arguments' => $arguments), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $name = $this->getNode('filter')->getAttribute('value'); + if (false === $filter = $compiler->getEnvironment()->getFilter($name)) { + throw new Twig_Error_Syntax(sprintf('The filter "%s" does not exist', $name), $this->getLine()); + } + + // The default filter is intercepted when the filtered value + // is a name (like obj) or an attribute (like obj.attr) + // In such a case, it's compiled to {{ obj is defined ? obj|default('bar') : 'bar' }} + if ('default' === $name && ($this->getNode('node') instanceof Twig_Node_Expression_Name || $this->getNode('node') instanceof Twig_Node_Expression_GetAttr)) { + $compiler->raw('('); + if ($this->getNode('node') instanceof Twig_Node_Expression_Name) { + $testMap = $compiler->getEnvironment()->getTests(); + $compiler + ->raw($testMap['defined']->compile().'(') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw(', $context)') + ; + } elseif ($this->getNode('node') instanceof Twig_Node_Expression_GetAttr) { + $this->getNode('node')->setAttribute('is_defined_test', true); + $compiler->subcompile($this->getNode('node')); + } + + $compiler->raw(' ? '); + $this->compileFilter($compiler, $filter); + $compiler->raw(' : '); + $compiler->subcompile($this->getNode('arguments')->getNode(0)); + $compiler->raw(')'); + } else { + $this->compileFilter($compiler, $filter); + } + } + + protected function compileFilter(Twig_Compiler $compiler, Twig_FilterInterface $filter) + { + $compiler + ->raw($filter->compile().'(') + ->raw($filter->needsEnvironment() ? '$this->env, ' : '') + ->raw($filter->needsContext() ? '$context, ' : '') + ; + + $this->getNode('node')->compile($compiler); + + foreach ($this->getNode('arguments') as $node) { + $compiler + ->raw(', ') + ->subcompile($node) + ; + } + + $compiler->raw(')'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Function.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Function.php new file mode 100755 index 0000000..81d243b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Function.php @@ -0,0 +1,43 @@ + $name, 'arguments' => $arguments), array(), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $function = $compiler->getEnvironment()->getFunction($this->getNode('name')->getAttribute('name')); + if (false === $function) { + throw new Twig_Error_Syntax(sprintf('The function "%s" does not exist', $this->getNode('name')->getAttribute('name')), $this->getLine()); + } + + $compiler + ->raw($function->compile().'(') + ->raw($function->needsEnvironment() ? '$this->env, ' : '') + ->raw($function->needsContext() ? '$context, ' : '') + ; + + $first = true; + foreach ($this->getNode('arguments') as $node) { + if (!$first) { + $compiler->raw(', '); + } else { + $first = false; + } + $compiler->subcompile($node); + } + + $compiler->raw(')'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/GetAttr.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/GetAttr.php new file mode 100755 index 0000000..0c66f04 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/GetAttr.php @@ -0,0 +1,47 @@ + $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('$this->getAttribute(') + ->subcompile($this->getNode('node')) + ->raw(', ') + ->subcompile($this->getNode('attribute')) + ->raw(', array(') + ; + + foreach ($this->getNode('arguments') as $node) { + $compiler + ->subcompile($node) + ->raw(', ') + ; + } + + $compiler + ->raw('), ') + ->repr($this->getAttribute('type')) + ->raw($this->hasAttribute('is_defined_test') ? ', true' : ', false') + ->raw(sprintf(', %d', $this->lineno)) + ->raw(')'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Name.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Name.php new file mode 100755 index 0000000..6c03e93 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Name.php @@ -0,0 +1,33 @@ + $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + if ('_self' === $this->getAttribute('name')) { + $compiler->raw('$this'); + } elseif ('_context' === $this->getAttribute('name')) { + $compiler->raw('$context'); + } elseif ('_charset' === $this->getAttribute('name')) { + $compiler->raw('$this->env->getCharset()'); + } elseif ($compiler->getEnvironment()->isStrictVariables()) { + $compiler->raw(sprintf('$this->getContext($context, \'%s\', \'%s\')', $this->getAttribute('name'), $this->lineno)); + } else { + $compiler->raw(sprintf('(isset($context[\'%s\']) ? $context[\'%s\'] : null)', $this->getAttribute('name'), $this->getAttribute('name'))); + } + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Parent.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Parent.php new file mode 100755 index 0000000..7be682d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Parent.php @@ -0,0 +1,39 @@ + + */ +class Twig_Node_Expression_Parent extends Twig_Node_Expression +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct(array(), array('name' => $name), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw("\$this->renderParentBlock(") + ->string($this->getAttribute('name')) + ->raw(", \$context, \$blocks)") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Test.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Test.php new file mode 100755 index 0000000..4995bc7 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Test.php @@ -0,0 +1,62 @@ + $node, 'arguments' => $arguments), array('name' => $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $testMap = $compiler->getEnvironment()->getTests(); + if (!isset($testMap[$this->getAttribute('name')])) { + throw new Twig_Error_Syntax(sprintf('The test "%s" does not exist', $this->getAttribute('name')), $this->getLine()); + } + + // defined is a special case + if ('defined' === $this->getAttribute('name')) { + if ($this->getNode('node') instanceof Twig_Node_Expression_Name) { + $compiler + ->raw($testMap['defined']->compile().'(') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw(', $context)') + ; + } elseif ($this->getNode('node') instanceof Twig_Node_Expression_GetAttr) { + $this->getNode('node')->setAttribute('is_defined_test', true); + $compiler->subcompile($this->getNode('node')); + } else { + throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine()); + } + return; + } + + $compiler + ->raw($testMap[$this->getAttribute('name')]->compile().'(') + ->subcompile($this->getNode('node')) + ; + + if (null !== $this->getNode('arguments')) { + $compiler->raw(', '); + + $max = count($this->getNode('arguments')) - 1; + foreach ($this->getNode('arguments') as $i => $node) { + $compiler->subcompile($node); + + if ($i != $max) { + $compiler->raw(', '); + } + } + } + + $compiler->raw(')'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary.php new file mode 100755 index 0000000..c514388 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary.php @@ -0,0 +1,30 @@ + $node), array(), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->raw('('); + $this->operator($compiler); + $compiler + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } + + abstract public function operator(Twig_Compiler $compiler); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Neg.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Neg.php new file mode 100755 index 0000000..2a3937e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Neg.php @@ -0,0 +1,18 @@ +raw('-'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Not.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Not.php new file mode 100755 index 0000000..f94073c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Not.php @@ -0,0 +1,18 @@ +raw('!'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Pos.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Pos.php new file mode 100755 index 0000000..04edb52 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Expression/Unary/Pos.php @@ -0,0 +1,18 @@ +raw('+'); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/For.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/For.php new file mode 100755 index 0000000..d565236 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/For.php @@ -0,0 +1,119 @@ + + */ +class Twig_Node_For extends Twig_Node +{ + public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $lineno, $tag = null) + { + parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => true), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + // the (array) cast bypasses a PHP 5.2.6 bug + ->write("\$context['_parent'] = (array) \$context;\n") + ->write("\$context['_seq'] = twig_ensure_traversable(") + ->subcompile($this->getNode('seq')) + ->raw(");\n") + ; + + if (null !== $this->getNode('else')) { + $compiler->write("\$context['_iterated'] = false;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("\$context['loop'] = array(\n") + ->write(" 'parent' => \$context['_parent'],\n") + ->write(" 'index0' => 0,\n") + ->write(" 'index' => 1,\n") + ->write(" 'first' => true,\n") + ->write(");\n") + ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n") + ->indent() + ->write("\$length = count(\$context['_seq']);\n") + ->write("\$context['loop']['revindex0'] = \$length - 1;\n") + ->write("\$context['loop']['revindex'] = \$length;\n") + ->write("\$context['loop']['length'] = \$length;\n") + ->write("\$context['loop']['last'] = 1 === \$length;\n") + ->outdent() + ->write("}\n") + ; + } + + $compiler + ->write("foreach (\$context['_seq'] as ") + ->subcompile($this->getNode('key_target')) + ->raw(" => ") + ->subcompile($this->getNode('value_target')) + ->raw(") {\n") + ->indent() + ; + + $compiler->subcompile($this->getNode('body')); + + if (null !== $this->getNode('else')) { + $compiler->write("\$context['_iterated'] = true;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("++\$context['loop']['index0'];\n") + ->write("++\$context['loop']['index'];\n") + ->write("\$context['loop']['first'] = false;\n") + ->write("if (isset(\$context['loop']['length'])) {\n") + ->indent() + ->write("--\$context['loop']['revindex0'];\n") + ->write("--\$context['loop']['revindex'];\n") + ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") + ->outdent() + ->write("}\n") + ; + } + + $compiler + ->outdent() + ->write("}\n") + ; + + if (null !== $this->getNode('else')) { + $compiler + ->write("if (!\$context['_iterated']) {\n") + ->indent() + ->subcompile($this->getNode('else')) + ->outdent() + ->write("}\n") + ; + } + + $compiler->write("\$_parent = \$context['_parent'];\n"); + + // remove some "private" loop variables (needed for nested loops) + $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); + + // keep the values set in the inner context for variables defined in the outer context + $compiler->write("\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));\n"); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/If.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/If.php new file mode 100755 index 0000000..b222222 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/If.php @@ -0,0 +1,67 @@ + + */ +class Twig_Node_If extends Twig_Node +{ + public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null) + { + parent::__construct(array('tests' => $tests, 'else' => $else), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + for ($i = 0; $i < count($this->getNode('tests')); $i += 2) { + if ($i > 0) { + $compiler + ->outdent() + ->write("} elseif (") + ; + } else { + $compiler + ->write('if (') + ; + } + + $compiler + ->subcompile($this->getNode('tests')->getNode($i)) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('tests')->getNode($i + 1)) + ; + } + + if ($this->hasNode('else') && null !== $this->getNode('else')) { + $compiler + ->outdent() + ->write("} else {\n") + ->indent() + ->subcompile($this->getNode('else')) + ; + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Import.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Import.php new file mode 100755 index 0000000..746284d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Import.php @@ -0,0 +1,51 @@ + + */ +class Twig_Node_Import extends Twig_Node +{ + public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $var, $lineno, $tag = null) + { + parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('var')) + ->raw(' = ') + ; + + if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) { + $compiler->raw("\$this"); + } else { + $compiler + ->raw('$this->env->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(", true)") + ; + } + + $compiler->raw(";\n"); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Include.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Include.php new file mode 100755 index 0000000..75ec6cc --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Include.php @@ -0,0 +1,76 @@ + + */ +class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $lineno, $tag = null) + { + parent::__construct(array('expr' => $expr, 'variables' => $variables), array('only' => (Boolean) $only), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) { + $compiler + ->write("\$this->env->loadTemplate(") + ->subcompile($this->getNode('expr')) + ->raw(")->display(") + ; + } else { + $compiler + ->write("\$template = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("if (!\$template") + ->raw(" instanceof Twig_Template) {\n") + ->indent() + ->write("\$template = \$this->env->loadTemplate(\$template);\n") + ->outdent() + ->write("}\n") + ->write('$template->display(') + ; + } + + if (false === $this->getAttribute('only')) { + if (null === $this->getNode('variables')) { + $compiler->raw('$context'); + } else { + $compiler + ->raw('array_merge($context, ') + ->subcompile($this->getNode('variables')) + ->raw(')') + ; + } + } else { + if (null === $this->getNode('variables')) { + $compiler->raw('array()'); + } else { + $compiler->subcompile($this->getNode('variables')); + } + } + + $compiler->raw(");\n"); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Macro.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Macro.php new file mode 100755 index 0000000..2bfe16f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Macro.php @@ -0,0 +1,65 @@ + + */ +class Twig_Node_Macro extends Twig_Node +{ + public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null) + { + parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $arguments = array(); + foreach ($this->getNode('arguments') as $argument) { + $arguments[] = '$'.$argument->getAttribute('name').' = null'; + } + + $compiler + ->addDebugInfo($this) + ->write(sprintf("public function get%s(%s)\n", $this->getAttribute('name'), implode(', ', $arguments)), "{\n") + ->indent() + ->write("\$context = array_merge(\$this->env->getGlobals(), array(\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $argument) { + $compiler + ->write('') + ->string($argument->getAttribute('name')) + ->raw(' => $'.$argument->getAttribute('name')) + ->raw(",\n") + ; + } + + $compiler + ->outdent() + ->write("));\n\n") + ->write("ob_start();\n") + ->subcompile($this->getNode('body')) + ->raw("\n") + ->write("return ob_get_clean();\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Module.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Module.php new file mode 100755 index 0000000..111f029 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Module.php @@ -0,0 +1,205 @@ + + */ +class Twig_Node_Module extends Twig_Node +{ + public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, $filename) + { + parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros), array('filename' => $filename), 1); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $this->compileTemplate($compiler); + } + + protected function compileTemplate(Twig_Compiler $compiler) + { + $this->compileClassHeader($compiler); + + if (count($this->getNode('blocks'))) { + $this->compileConstructor($compiler); + } + + $this->compileGetParent($compiler); + + $this->compileDisplayHeader($compiler); + + $this->compileDisplayBody($compiler); + + $this->compileDisplayFooter($compiler); + + $compiler->subcompile($this->getNode('blocks')); + + $this->compileMacros($compiler); + + $this->compileGetTemplateName($compiler); + + $this->compileClassFooter($compiler); + } + + protected function compileGetParent(Twig_Compiler $compiler) + { + if (null === $this->getNode('parent')) { + return; + } + + $compiler + ->write("public function getParent(array \$context)\n", "{\n") + ->indent() + ->write("if (null === \$this->parent) {\n") + ->indent(); + ; + + if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { + $compiler + ->write("\$this->parent = \$this->env->loadTemplate(") + ->subcompile($this->getNode('parent')) + ->raw(");\n") + ; + } else { + $compiler + ->write("\$this->parent = ") + ->subcompile($this->getNode('parent')) + ->raw(";\n") + ->write("if (!\$this->parent") + ->raw(" instanceof Twig_Template) {\n") + ->indent() + ->write("\$this->parent = \$this->env->loadTemplate(\$this->parent);\n") + ->outdent() + ->write("}\n") + ; + } + + $compiler + ->outdent() + ->write("}\n\n") + ->write("return \$this->parent;\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDisplayBody(Twig_Compiler $compiler) + { + $compiler->write("\$context = array_merge(\$this->env->getGlobals(), \$context);\n\n"); + + if (null !== $this->getNode('parent')) { + // remove all output nodes + foreach ($this->getNode('body') as $node) { + if (!$node instanceof Twig_NodeOutputInterface) { + $compiler->subcompile($node); + } + } + + $compiler + ->write("\$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks));\n") + ; + } else { + $compiler->subcompile($this->getNode('body')); + } + } + + protected function compileClassHeader(Twig_Compiler $compiler) + { + $compiler + ->write("write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n") + ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'))) + ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) + ->write("{\n") + ->indent() + ; + + if (null !== $this->getNode('parent')) { + $compiler->write("protected \$parent;\n\n"); + } + } + + protected function compileConstructor(Twig_Compiler $compiler) + { + $compiler + ->write("public function __construct(Twig_Environment \$env)\n", "{\n") + ->indent() + ->write("parent::__construct(\$env);\n\n") + ->write("\$this->blocks = array(\n") + ->indent() + ; + + foreach ($this->getNode('blocks') as $name => $node) { + $compiler + ->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name)) + ; + } + + $compiler + ->outdent() + ->write(");\n") + ->outdent() + ->write("}\n\n"); + ; + } + + protected function compileDisplayHeader(Twig_Compiler $compiler) + { + $compiler + ->write("public function display(array \$context, array \$blocks = array())\n", "{\n") + ->indent() + ; + } + + protected function compileDisplayFooter(Twig_Compiler $compiler) + { + $compiler + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassFooter(Twig_Compiler $compiler) + { + $compiler + ->outdent() + ->write("}\n") + ; + } + + protected function compileMacros(Twig_Compiler $compiler) + { + $compiler->subcompile($this->getNode('macros')); + } + + protected function compileGetTemplateName(Twig_Compiler $compiler) + { + $compiler + ->write("public function getTemplateName()\n", "{\n") + ->indent() + ->write('return ') + ->repr($this->getAttribute('filename')) + ->raw(";\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Print.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Print.php new file mode 100755 index 0000000..49047c9 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Print.php @@ -0,0 +1,40 @@ + + */ +class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) + { + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Sandbox.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Sandbox.php new file mode 100755 index 0000000..0a513c0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Sandbox.php @@ -0,0 +1,48 @@ + + */ +class Twig_Node_Sandbox extends Twig_Node +{ + public function __construct(Twig_NodeInterface $body, $lineno, $tag = null) + { + parent::__construct(array('body' => $body), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("\$sandbox = \$this->env->getExtension('sandbox');\n") + ->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n") + ->indent() + ->write("\$sandbox->enableSandbox();\n") + ->outdent() + ->write("}\n") + ->subcompile($this->getNode('body')) + ->write("if (!\$alreadySandboxed) {\n") + ->indent() + ->write("\$sandbox->disableSandbox();\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedModule.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedModule.php new file mode 100755 index 0000000..35c7aff --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedModule.php @@ -0,0 +1,71 @@ + + */ +class Twig_Node_SandboxedModule extends Twig_Node_Module +{ + protected $usedFilters; + protected $usedTags; + protected $usedFunctions; + + public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions) + { + parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag()); + + $this->usedFilters = $usedFilters; + $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; + } + + protected function compileDisplayBody(Twig_Compiler $compiler) + { + if (null === $this->getNode('parent')) { + $compiler->write("\$this->checkSecurity();\n"); + } + + parent::compileDisplayBody($compiler); + } + + protected function compileDisplayFooter(Twig_Compiler $compiler) + { + parent::compileDisplayFooter($compiler); + + $compiler + ->write("protected function checkSecurity() {\n") + ->indent() + ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n") + ->indent() + ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n") + ->write(!$this->usedFilters ? "array(),\n" : "array('".implode('\', \'', $this->usedFilters)."'),\n") + ->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n") + ->outdent() + ->write(");\n") + ; + + if (null !== $this->getNode('parent')) { + $compiler + ->raw("\n") + ->write("\$this->parent->checkSecurity();\n") + ; + } + + $compiler + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedPrint.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedPrint.php new file mode 100755 index 0000000..619ff99 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/SandboxedPrint.php @@ -0,0 +1,68 @@ + + */ +class Twig_Node_SandboxedPrint extends Twig_Node_Print +{ + public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) + { + parent::__construct($expr, $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('if (is_object(') + ->raw('$_tmp = ') + ->subcompile($this->removeNodeFilter($this->getNode('expr'))) + ->raw(')) {'."\n") + ->indent() + ->write('$this->env->getExtension(\'sandbox\')->checkMethodAllowed(') + ->raw('$_tmp, \'__toString\');'."\n") + ->outdent() + ->write('}'."\n") + ; + + parent::compile($compiler); + } + + /** + * Removes node filters. + * + * This is mostly needed when another visitor adds filters (like the escaper one). + * + * @param Twig_Node $node A Node + */ + protected function removeNodeFilter($node) + { + if ($node instanceof Twig_Node_Expression_Filter) { + return $this->removeNodeFilter($node->getNode('node')); + } + + return $node; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Set.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Set.php new file mode 100755 index 0000000..04be00e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Set.php @@ -0,0 +1,79 @@ + + */ +class Twig_Node_Set extends Twig_Node +{ + public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null) + { + parent::__construct(array('names' => $names, 'values' => $values), array('capture' => $capture), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if (count($this->getNode('names')) > 1) { + $compiler->write('list('); + foreach ($this->getNode('names') as $idx => $node) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($node); + } + $compiler->raw(')'); + } else { + if ($this->getAttribute('capture')) { + $compiler + ->write("ob_start();\n") + ->subcompile($this->getNode('values')) + ; + } + + $compiler->subcompile($this->getNode('names'), false); + + if ($this->getAttribute('capture')) { + $compiler->raw(" = ob_get_clean()"); + } + } + + if (!$this->getAttribute('capture')) { + $compiler->raw(' = '); + + if (count($this->getNode('names')) > 1) { + $compiler->write('array('); + foreach ($this->getNode('values') as $idx => $value) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($value); + } + $compiler->raw(')'); + } else { + $compiler->subcompile($this->getNode('values')); + } + } + + $compiler->raw(";\n"); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Spaceless.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Spaceless.php new file mode 100755 index 0000000..8c6300d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Spaceless.php @@ -0,0 +1,41 @@ + + */ +class Twig_Node_Spaceless extends Twig_Node +{ + public function __construct(Twig_NodeInterface $body, $lineno, $tag = 'spaceless') + { + parent::__construct(array('body' => $body), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("ob_start();\n") + ->subcompile($this->getNode('body')) + ->write("echo trim(preg_replace('/>\s+<', ob_get_clean()));\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Node/Text.php b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Text.php new file mode 100755 index 0000000..7812bc2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Node/Text.php @@ -0,0 +1,40 @@ + + */ +class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct($data, $lineno) + { + parent::__construct(array(), array('data' => $data), $lineno); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->string($this->getAttribute('data')) + ->raw(";\n") + ; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeInterface.php new file mode 100755 index 0000000..5e17b77 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeInterface.php @@ -0,0 +1,30 @@ + + */ +interface Twig_NodeInterface +{ + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + function compile(Twig_Compiler $compiler); + + function getLine(); + + function getNodeTag(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeOutputInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeOutputInterface.php new file mode 100755 index 0000000..a75154c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeOutputInterface.php @@ -0,0 +1,20 @@ + + */ +interface Twig_NodeOutputInterface +{ +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeTraverser.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeTraverser.php new file mode 100755 index 0000000..e7335f0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeTraverser.php @@ -0,0 +1,89 @@ + + */ +class Twig_NodeTraverser +{ + protected $env; + protected $visitors; + + /** + * Constructor. + * + * @param Twig_Environment $env A Twig_Environment instance + * @param array $visitors An array of Twig_NodeVisitorInterface instances + */ + public function __construct(Twig_Environment $env, array $visitors = array()) + { + $this->env = $env; + $this->visitors = array(); + foreach ($visitors as $visitor) { + $this->addVisitor($visitor); + } + } + + /** + * Adds a visitor. + * + * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance + */ + public function addVisitor(Twig_NodeVisitorInterface $visitor) + { + if (!isset($this->visitors[$visitor->getPriority()])) { + $this->visitors[$visitor->getPriority()] = array(); + } + + $this->visitors[$visitor->getPriority()][] = $visitor; + } + + /** + * Traverses a node and calls the registered visitors. + * + * @param Twig_NodeInterface $node A Twig_NodeInterface instance + */ + public function traverse(Twig_NodeInterface $node) + { + ksort($this->visitors); + foreach ($this->visitors as $visitors) { + foreach ($visitors as $visitor) { + $node = $this->traverseForVisitor($visitor, $node); + } + } + + return $node; + } + + protected function traverseForVisitor($visitor, Twig_NodeInterface $node = null) + { + if (null === $node) { + return null; + } + + $node = $visitor->enterNode($node, $this->env); + + foreach ($node as $k => $n) { + if (false !== $n = $this->traverseForVisitor($visitor, $n)) { + $node->setNode($k, $n); + } else { + $node->removeNode($k); + } + } + + return $visitor->leaveNode($node, $this->env); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Escaper.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Escaper.php new file mode 100755 index 0000000..468f97a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Escaper.php @@ -0,0 +1,161 @@ + + */ +class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface +{ + protected $statusStack = array(); + protected $blocks = array(); + + protected $safeAnalysis; + protected $traverser; + + function __construct() + { + $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis(); + } + + /** + * Called before child nodes are visited. + * + * @param Twig_NodeInterface $node The node to visit + * @param Twig_Environment $env The Twig environment instance + * + * @param Twig_NodeInterface The modified node + */ + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_AutoEscape) { + $this->statusStack[] = $node->getAttribute('value'); + } elseif ($node instanceof Twig_Node_Block) { + $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); + } + + return $node; + } + + /** + * Called after child nodes are visited. + * + * @param Twig_NodeInterface $node The node to visit + * @param Twig_Environment $env The Twig environment instance + * + * @param Twig_NodeInterface The modified node + */ + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Expression_Filter) { + return $this->preEscapeFilterNode($node, $env); + } elseif ($node instanceof Twig_Node_Print) { + return $this->escapePrintNode($node, $env, $this->needEscaping($env)); + } + + if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) { + array_pop($this->statusStack); + } elseif ($node instanceof Twig_Node_BlockReference) { + $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); + } + + return $node; + } + + protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type) + { + if (false === $type) { + return $node; + } + + $expression = $node->getNode('expr'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + $class = get_class($node); + + return new $class( + $this->getEscaperFilter($type, $expression), + $node->getLine() + ); + } + + protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env) + { + $name = $filter->getNode('filter')->getAttribute('value'); + + if (false !== $f = $env->getFilter($name)) { + $type = $f->getPreEscape(); + if (null === $type) { + return $filter; + } + + $node = $filter->getNode('node'); + if ($this->isSafeFor($type, $node, $env)) { + return $filter; + } + + $filter->setNode('node', $this->getEscaperFilter($type, $node)); + + return $filter; + } + + return $filter; + } + + protected function isSafeFor($type, Twig_NodeInterface $expression, $env) + { + $safe = $this->safeAnalysis->getSafe($expression); + + if (null === $safe) { + if (null === $this->traverser) { + $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis)); + } + $this->traverser->traverse($expression); + $safe = $this->safeAnalysis->getSafe($expression); + } + + return in_array($type, $safe) || in_array('all', $safe); + } + + protected function needEscaping(Twig_Environment $env) + { + if (count($this->statusStack)) { + return $this->statusStack[count($this->statusStack) - 1]; + } + + if ($env->hasExtension('escaper') && $env->getExtension('escaper')->isGlobal()) { + return 'html'; + } + + return false; + } + + protected function getEscaperFilter($type, Twig_NodeInterface $node) + { + $line = $node->getLine(); + $name = new Twig_Node_Expression_Constant('escape', $line); + $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line))); + return new Twig_Node_Expression_Filter($node, $name, $args, $line); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Optimizer.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Optimizer.php new file mode 100755 index 0000000..87b38e2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Optimizer.php @@ -0,0 +1,173 @@ + + */ +class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface +{ + const OPTIMIZE_ALL = -1; + const OPTIMIZE_NONE = 0; + const OPTIMIZE_FOR = 2; + const OPTIMIZE_RAW_FILTER = 4; + + protected $loops = array(); + protected $optimizers; + + /** + * Constructor. + * + * @param integer $optimizers The optimizer mode + */ + public function __construct($optimizers = -1) + { + if (null === $optimizers) { + $mode = self::OPTIMIZE_ALL; + } elseif (!is_int($optimizers) || $optimizers > 2) { + throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + } + + $this->optimizers = $optimizers; + } + + /** + * {@inheritdoc} + */ + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->enterOptimizeFor($node, $env); + } + + return $node; + } + + /** + * {@inheritdoc} + */ + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->leaveOptimizeFor($node, $env); + } + + if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { + $node = $this->optimizeRawFilter($node, $env); + } + + return $node; + } + + /** + * Removes "raw" filters. + * + * @param Twig_NodeInterface $node A Node + * @param Twig_Environment $env The current Twig environment + */ + protected function optimizeRawFilter($node, $env) + { + if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { + return $node->getNode('node'); + } + + return $node; + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + * + * @param Twig_NodeInterface $node A Node + * @param Twig_Environment $env The current Twig environment + */ + protected function enterOptimizeFor($node, $env) + { + if ($node instanceof Twig_Node_For) { + // disable the loop variable by default + $node->setAttribute('with_loop', false); + array_unshift($this->loops, $node); + } elseif (!$this->loops) { + // we are outside a loop + return; + } + + // when do we need to add the loop variable back? + + // the loop variable is referenced for the current loop + elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { + $this->addLoopToCurrent(); + } + + // block reference + elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { + $this->addLoopToCurrent(); + } + + // include without the only attribute + elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) { + $this->addLoopToAll(); + } + + // the loop variable is referenced via an attribute + elseif ($node instanceof Twig_Node_Expression_GetAttr + && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant + || 'parent' === $node->getNode('attribute')->getAttribute('value') + ) + && (true === $this->loops[0]->getAttribute('with_loop') + || ($node->getNode('node') instanceof Twig_Node_Expression_Name + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) + ) { + $this->addLoopToAll(); + } + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + * + * @param Twig_NodeInterface $node A Node + * @param Twig_Environment $env The current Twig environment + */ + protected function leaveOptimizeFor($node, $env) + { + if ($node instanceof Twig_Node_For) { + array_shift($this->loops); + } + } + + protected function addLoopToCurrent() + { + $this->loops[0]->setAttribute('with_loop', true); + } + + protected function addLoopToAll() + { + foreach ($this->loops as $loop) { + $loop->setAttribute('with_loop', true); + } + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 255; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/SafeAnalysis.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/SafeAnalysis.php new file mode 100755 index 0000000..4579f4a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/SafeAnalysis.php @@ -0,0 +1,101 @@ +data[$hash])) { + foreach($this->data[$hash] as $bucket) { + if ($bucket['key'] === $node) { + return $bucket['value']; + } + } + } + return null; + } + + protected function setSafe(Twig_NodeInterface $node, array $safe) + { + $hash = spl_object_hash($node); + if (isset($this->data[$hash])) { + foreach($this->data[$hash] as &$bucket) { + if ($bucket['key'] === $node) { + $bucket['value'] = $safe; + return; + } + } + } + $this->data[$hash][] = array( + 'key' => $node, + 'value' => $safe, + ); + } + + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + return $node; + } + + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Expression_Constant) { + // constants are marked safe for all + $this->setSafe($node, array('all')); + } elseif ($node instanceof Twig_Node_Expression_Conditional) { + // instersect safeness of both operands + $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); + $this->setSafe($node, $safe); + } elseif ($node instanceof Twig_Node_Expression_Filter) { + // filter expression is safe when the filter is safe + $name = $node->getNode('filter')->getAttribute('value'); + $args = $node->getNode('arguments'); + if (false !== $filter = $env->getFilter($name)) { + $this->setSafe($node, $filter->getSafe($args)); + } else { + $this->setSafe($node, array()); + } + } elseif ($node instanceof Twig_Node_Expression_Function) { + // function expression is safe when the function is safe + $name = $node->getNode('name')->getAttribute('name'); + $args = $node->getNode('arguments'); + $function = $env->getFunction($name); + if (false !== $function) { + $this->setSafe($node, $function->getSafe($args)); + } else { + $this->setSafe($node, array()); + } + } else { + $this->setSafe($node, array()); + } + + return $node; + } + + protected function intersectSafe(array $a = null, array $b = null) + { + if (null === $a || null === $b) { + return array(); + } + + if (in_array('all', $a)) { + return $b; + } + + if (in_array('all', $b)) { + return $a; + } + + return array_intersect($a, $b); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Sandbox.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Sandbox.php new file mode 100755 index 0000000..d910673 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitor/Sandbox.php @@ -0,0 +1,93 @@ + + */ +class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface +{ + protected $inAModule = false; + protected $tags; + protected $filters; + protected $functions; + + /** + * Called before child nodes are visited. + * + * @param Twig_NodeInterface $node The node to visit + * @param Twig_Environment $env The Twig environment instance + * + * @param Twig_NodeInterface The modified node + */ + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + $this->inAModule = true; + $this->tags = array(); + $this->filters = array(); + $this->functions = array(); + + return $node; + } elseif ($this->inAModule) { + // look for tags + if ($node->getNodeTag()) { + $this->tags[] = $node->getNodeTag(); + } + + // look for filters + if ($node instanceof Twig_Node_Expression_Filter) { + $this->filters[] = $node->getNode('filter')->getAttribute('value'); + } + + // look for functions + if ($node instanceof Twig_Node_Expression_Function) { + $this->functions[] = $node->getNode('name')->getAttribute('name'); + } + + // wrap print to check __toString() calls + if ($node instanceof Twig_Node_Print) { + return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag()); + } + } + + return $node; + } + + /** + * Called after child nodes are visited. + * + * @param Twig_NodeInterface $node The node to visit + * @param Twig_Environment $env The Twig environment instance + * + * @param Twig_NodeInterface The modified node + */ + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + $this->inAModule = false; + + return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions)); + } + + return $node; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitorInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitorInterface.php new file mode 100755 index 0000000..c73aca5 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/NodeVisitorInterface.php @@ -0,0 +1,48 @@ + + */ +interface Twig_NodeVisitorInterface +{ + /** + * Called before child nodes are visited. + * + * @param Twig_NodeInterface $node The node to visit + * @param Twig_Environment $env The Twig environment instance + * + * @param Twig_NodeInterface The modified node + */ + function enterNode(Twig_NodeInterface $node, Twig_Environment $env); + + /** + * Called after child nodes are visited. + * + * @param Twig_NodeInterface $node The node to visit + * @param Twig_Environment $env The Twig environment instance + * + * @param Twig_NodeInterface The modified node + */ + function leaveNode(Twig_NodeInterface $node, Twig_Environment $env); + + /** + * Returns the priority for this visitor. + * + * Priority should be between -10 and 10 (0 is the default). + * + * @return integer The priority level + */ + function getPriority(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Parser.php b/lib/Twig-1.0.0-RC1/lib/Twig/Parser.php new file mode 100755 index 0000000..88d4181 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Parser.php @@ -0,0 +1,287 @@ + + */ +class Twig_Parser implements Twig_ParserInterface +{ + protected $stream; + protected $parent; + protected $handlers; + protected $visitors; + protected $expressionParser; + protected $blocks; + protected $blockStack; + protected $macros; + protected $env; + protected $reservedMacroNames; + protected $importedFunctions; + protected $tmpVarCount; + + /** + * Constructor. + * + * @param Twig_Environment $env A Twig_Environment instance + */ + public function __construct(Twig_Environment $env) + { + $this->env = $env; + } + + public function getVarName() + { + return sprintf('__internal_%s_%d', substr($this->env->getTemplateClass($this->stream->getFilename()), strlen($this->env->getTemplateClassPrefix())), ++$this->tmpVarCount); + } + + /** + * Converts a token stream to a node tree. + * + * @param Twig_TokenStream $stream A token stream instance + * + * @return Twig_Node_Module A node tree + */ + public function parse(Twig_TokenStream $stream) + { + $this->tmpVarCount = 0; + + // tag handlers + $this->handlers = $this->env->getTokenParsers(); + $this->handlers->setParser($this); + + // node visitors + $this->visitors = $this->env->getNodeVisitors(); + + if (null === $this->expressionParser) { + $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators()); + } + + $this->stream = $stream; + $this->parent = null; + $this->blocks = array(); + $this->macros = array(); + $this->blockStack = array(); + $this->importedFunctions = array(array()); + + try { + $body = $this->subparse(null); + + if (null !== $this->parent) { + $this->checkBodyNodes($body); + } + } catch (Twig_Error_Syntax $e) { + if (null === $e->getFilename()) { + $e->setFilename($this->stream->getFilename()); + } + + throw $e; + } + + $node = new Twig_Node_Module($body, $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), $this->stream->getFilename()); + + $traverser = new Twig_NodeTraverser($this->env, $this->visitors); + + return $traverser->traverse($node); + } + + public function subparse($test, $dropNeedle = false) + { + $lineno = $this->getCurrentToken()->getLine(); + $rv = array(); + while (!$this->stream->isEOF()) { + switch ($this->getCurrentToken()->getType()) { + case Twig_Token::TEXT_TYPE: + $token = $this->stream->next(); + $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine()); + break; + + case Twig_Token::VAR_START_TYPE: + $token = $this->stream->next(); + $expr = $this->expressionParser->parseExpression(); + $this->stream->expect(Twig_Token::VAR_END_TYPE); + $rv[] = new Twig_Node_Print($expr, $token->getLine()); + break; + + case Twig_Token::BLOCK_START_TYPE: + $this->stream->next(); + $token = $this->getCurrentToken(); + + if ($token->getType() !== Twig_Token::NAME_TYPE) { + throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine()); + } + + if (null !== $test && call_user_func($test, $token)) { + if ($dropNeedle) { + $this->stream->next(); + } + + if (1 === count($rv)) { + return $rv[0]; + } + + return new Twig_Node($rv, array(), $lineno); + } + + $subparser = $this->handlers->getTokenParser($token->getValue()); + if (null === $subparser) { + throw new Twig_Error_Syntax(sprintf('Unknown tag name "%s"', $token->getValue()), $token->getLine()); + } + + $this->stream->next(); + + $node = $subparser->parse($token); + if (null !== $node) { + $rv[] = $node; + } + break; + + default: + throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.'); + } + } + + if (1 === count($rv)) { + return $rv[0]; + } + + return new Twig_Node($rv, array(), $lineno); + } + + public function addHandler($name, $class) + { + $this->handlers[$name] = $class; + } + + public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) + { + $this->visitors[] = $visitor; + } + + public function getBlockStack() + { + return $this->blockStack; + } + + public function peekBlockStack() + { + return $this->blockStack[count($this->blockStack) - 1]; + } + + public function popBlockStack() + { + array_pop($this->blockStack); + } + + public function pushBlockStack($name) + { + $this->blockStack[] = $name; + } + + public function hasBlock($name) + { + return isset($this->blocks[$name]); + } + + public function setBlock($name, $value) + { + $this->blocks[$name] = $value; + } + + public function hasMacro($name) + { + return isset($this->macros[$name]); + } + + public function setMacro($name, Twig_Node_Macro $node) + { + if (null === $this->reservedMacroNames) { + $this->reservedMacroNames = array(); + $r = new ReflectionClass($this->env->getBaseTemplateClass()); + foreach ($r->getMethods() as $method) { + $this->reservedMacroNames[] = $method->getName(); + } + } + + if (in_array($name, $this->reservedMacroNames)) { + throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine()); + } + + $this->macros[$name] = $node; + } + + public function addImportedFunction($alias, $name, Twig_Node_Expression $node) + { + $this->importedFunctions[0][$alias] = array('name' => $name, 'node' => $node); + } + + public function getImportedFunction($alias) + { + foreach ($this->importedFunctions as $functions) { + if (isset($functions[$alias])) { + return $functions[$alias]; + } + } + } + + public function pushLocalScope() + { + array_unshift($this->importedFunctions, array()); + } + + public function popLocalScope() + { + array_shift($this->importedFunctions); + } + + public function getExpressionParser() + { + return $this->expressionParser; + } + + public function getParent() + { + return $this->parent; + } + + public function setParent($parent) + { + $this->parent = $parent; + } + + public function getStream() + { + return $this->stream; + } + + public function getCurrentToken() + { + return $this->stream->getCurrent(); + } + + protected function checkBodyNodes($body) + { + // check that the body does not contain non-empty output nodes + foreach ($body as $node) + { + if ( + ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data'))) + || + (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface) + ) { + throw new Twig_Error_Syntax(sprintf('A template that extends another one cannot have a body (%s).', $node), $node->getLine()); + } + } + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/ParserInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/ParserInterface.php new file mode 100755 index 0000000..30cabc0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/ParserInterface.php @@ -0,0 +1,28 @@ + + */ +interface Twig_ParserInterface +{ + /** + * Converts a token stream to a node tree. + * + * @param Twig_TokenStream $stream A token stream instance + * + * @return Twig_Node_Module A node tree + */ + function parse(Twig_TokenStream $code); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityError.php b/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityError.php new file mode 100755 index 0000000..1fac69f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityError.php @@ -0,0 +1,20 @@ + + */ +class Twig_Sandbox_SecurityError extends Twig_Error +{ +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicy.php b/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicy.php new file mode 100755 index 0000000..0d9a1a8 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicy.php @@ -0,0 +1,116 @@ + + */ +class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface +{ + protected $allowedTags; + protected $allowedFilters; + protected $allowedMethods; + protected $allowedProperties; + protected $allowedFunctions; + + public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array(), array $allowedFunctions = array()) + { + $this->allowedTags = $allowedTags; + $this->allowedFilters = $allowedFilters; + $this->allowedMethods = $allowedMethods; + $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; + } + + public function setAllowedTags(array $tags) + { + $this->allowedTags = $tags; + } + + public function setAllowedFilters(array $filters) + { + $this->allowedFilters = $filters; + } + + public function setAllowedMethods(array $methods) + { + $this->allowedMethods = $methods; + } + + public function setAllowedProperties(array $properties) + { + $this->allowedProperties = $properties; + } + + public function setAllowedFunctions(array $functions) + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions) + { + foreach ($tags as $tag) { + if (!in_array($tag, $this->allowedTags)) { + throw new Twig_Sandbox_SecurityError(sprintf('Tag "%s" is not allowed.', $tag)); + } + } + + foreach ($filters as $filter) { + if (!in_array($filter, $this->allowedFilters)) { + throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter)); + } + } + + foreach ($functions as $function) { + if (!in_array($function, $this->allowedFunctions)) { + throw new Twig_Sandbox_SecurityError(sprintf('Function "%s" is not allowed.', $function)); + } + } + } + + public function checkMethodAllowed($obj, $method) + { + if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) { + return true; + } + + $allowed = false; + foreach ($this->allowedMethods as $class => $methods) { + if ($obj instanceof $class) { + $allowed = in_array($method, is_array($methods) ? $methods : array($methods)); + + break; + } + } + + if (!$allowed) { + throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); + } + } + + public function checkPropertyAllowed($obj, $property) + { + $allowed = false; + foreach ($this->allowedProperties as $class => $properties) { + if ($obj instanceof $class) { + $allowed = in_array($property, is_array($properties) ? $properties : array($properties)); + + break; + } + } + + if (!$allowed) { + throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, get_class($obj))); + } + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicyInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicyInterface.php new file mode 100755 index 0000000..2a771c4 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Sandbox/SecurityPolicyInterface.php @@ -0,0 +1,25 @@ + + */ +interface Twig_Sandbox_SecurityPolicyInterface +{ + function checkSecurity($tags, $filters, $functions); + + function checkMethodAllowed($obj, $method); + + function checkPropertyAllowed($obj, $method); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Template.php b/lib/Twig-1.0.0-RC1/lib/Twig/Template.php new file mode 100755 index 0000000..829d583 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Template.php @@ -0,0 +1,294 @@ + + */ +abstract class Twig_Template implements Twig_TemplateInterface +{ + static protected $cache = array(); + + protected $env; + protected $blocks; + + /** + * Constructor. + * + * @param Twig_Environment $env A Twig_Environment instance + */ + public function __construct(Twig_Environment $env) + { + $this->env = $env; + $this->blocks = array(); + } + + /** + * Returns the template name. + * + * @return string The template name + */ + public function getTemplateName() + { + return null; + } + + /** + * Returns the Twig environment. + * + * @return Twig_Environment The Twig environment + */ + public function getEnvironment() + { + return $this->env; + } + + /** + * Returns the parent template. + * + * @return Twig_TemplateInterface|false The parent template or false if there is no parent + */ + public function getParent(array $context) + { + return false; + } + + /** + * Displays a parent block. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + */ + public function displayParentBlock($name, array $context, array $blocks = array()) + { + if (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, $blocks); + } else { + throw new Twig_Error_Runtime('This template has no parent', -1, $this->getTemplateName()); + } + } + + /** + * Displays a block. + * + * @param string $name The block name to display + * @param array $context The context + * @param array $blocks The current set of blocks + */ + public function displayBlock($name, array $context, array $blocks = array()) + { + if (isset($blocks[$name])) { + $b = $blocks; + unset($b[$name]); + call_user_func($blocks[$name], $context, $b); + } elseif (isset($this->blocks[$name])) { + call_user_func($this->blocks[$name], $context, $blocks); + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks)); + } + } + + /** + * Renders a parent block. + * + * @param string $name The block name to render from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return string The rendered block + */ + public function renderParentBlock($name, array $context, array $blocks = array()) + { + ob_start(); + $this->displayParentBlock($name, $context, $blocks); + + return new Twig_Markup(ob_get_clean()); + } + + /** + * Renders a block. + * + * @param string $name The block name to render + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return string The rendered block + */ + public function renderBlock($name, array $context, array $blocks = array()) + { + ob_start(); + $this->displayBlock($name, $context, $blocks); + + return new Twig_Markup(ob_get_clean()); + } + + /** + * Returns whether a block exists or not. + * + * @param string $name The block name + * + * @return Boolean true if the block exists, false otherwise + */ + public function hasBlock($name) + { + return isset($this->blocks[$name]); + } + + /** + * Returns all block names. + * + * @return array An array of block names + */ + public function getBlockNames() + { + return array_keys($this->blocks); + } + + /** + * Renders the template with the given context and returns it as string. + * + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + */ + public function render(array $context) + { + ob_start(); + try { + $this->display($context); + } catch (Exception $e) { + ob_end_clean(); + + throw $e; + } + + return ob_get_clean(); + } + + /** + * Returns a variable from the context. + * + * @param array $context The context + * @param string $item The variable to return from the context + * @param integer $line The line where the variable is get + * + * @param mixed The variable value in the context + * + * @throws Twig_Error_Runtime if the variable does not exist + */ + protected function getContext($context, $item, $line = -1) + { + if (!array_key_exists($item, $context)) { + throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), $line, $this->getTemplateName()); + } + + return $context[$item]; + } + + /** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param integer $type The type of attribute (@see Twig_Node_Expression_GetAttr) + * @param Boolean $noStrictCheck Whether to throw an exception if the item does not exist ot not + * @param integer $line The line where the attribute is get + */ + protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_Node_Expression_GetAttr::TYPE_ANY, $noStrictCheck = false, $line = -1) + { + // array + if (Twig_Node_Expression_GetAttr::TYPE_METHOD !== $type) { + if ((is_array($object) || is_object($object) && $object instanceof ArrayAccess) && isset($object[$item])) { + return $object[$item]; + } + + if (Twig_Node_Expression_GetAttr::TYPE_ARRAY === $type) { + if (!$this->env->isStrictVariables() || $noStrictCheck) { + return null; + } + + if (is_object($object)) { + throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), $line, $this->getTemplateName()); + // array + } else { + throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), $line, $this->getTemplateName()); + } + } + } + + if (!is_object($object)) { + if (!$this->env->isStrictVariables() || $noStrictCheck) { + return null; + } + throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, $object), $line, $this->getTemplateName()); + } + + // get some information about the object + $class = get_class($object); + if (!isset(self::$cache[$class])) { + $r = new ReflectionClass($class); + self::$cache[$class] = array('methods' => array(), 'properties' => array()); + foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + self::$cache[$class]['methods'][strtolower($method->getName())] = true; + } + + foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + self::$cache[$class]['properties'][strtolower($property->getName())] = true; + } + } + + // object property + if (Twig_Node_Expression_GetAttr::TYPE_METHOD !== $type) { + if (isset(self::$cache[$class]['properties'][strtolower($item)]) || isset($object->$item)) { + if ($this->env->hasExtension('sandbox')) { + $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item); + } + + return $object->$item; + } + } + + // object method + $lcItem = strtolower($item); + if (isset(self::$cache[$class]['methods'][$lcItem])) { + $method = $item; + } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { + $method = 'get'.$item; + } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { + $method = 'is'.$item; + } elseif (isset(self::$cache[$class]['methods']['__call'])) { + $method = $item; + } else { + if (!$this->env->isStrictVariables() || $noStrictCheck) { + return null; + } + + throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), $line, $this->getTemplateName()); + } + + if ($this->env->hasExtension('sandbox')) { + $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method); + } + + $ret = call_user_func_array(array($object, $method), $arguments); + + if ($object instanceof Twig_TemplateInterface) { + return new Twig_Markup($ret); + } + + return $ret; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TemplateInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/TemplateInterface.php new file mode 100755 index 0000000..033eb7a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TemplateInterface.php @@ -0,0 +1,42 @@ + + */ +interface Twig_TemplateInterface +{ + /** + * Renders the template with the given context and returns it as string. + * + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + */ + function render(array $context); + + /** + * Displays the template with the given context. + * + * @param array $context An array of parameters to pass to the template + */ + function display(array $context); + + /** + * Returns the bound environment for this template. + * + * @return Twig_Environment The current environment + */ + function getEnvironment(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Test/Function.php b/lib/Twig-1.0.0-RC1/lib/Twig/Test/Function.php new file mode 100755 index 0000000..81750b6 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Test/Function.php @@ -0,0 +1,31 @@ + + */ +class Twig_Test_Function implements Twig_TestInterface +{ + protected $function; + + public function __construct($function) + { + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Test/Method.php b/lib/Twig-1.0.0-RC1/lib/Twig/Test/Method.php new file mode 100755 index 0000000..ea6a44c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Test/Method.php @@ -0,0 +1,32 @@ + + */ +class Twig_Test_Method implements Twig_TestInterface +{ + protected $extension, $method; + + public function __construct(Twig_ExtensionInterface $extension, $method) + { + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TestInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/TestInterface.php new file mode 100755 index 0000000..691ba14 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TestInterface.php @@ -0,0 +1,26 @@ + + */ +interface Twig_TestInterface +{ + /** + * Compiles a test. + * + * @return string The PHP code for the test + */ + function compile(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/Token.php b/lib/Twig-1.0.0-RC1/lib/Twig/Token.php new file mode 100755 index 0000000..f74453e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/Token.php @@ -0,0 +1,208 @@ + + */ +class Twig_Token +{ + protected $value; + protected $type; + protected $lineno; + + const EOF_TYPE = -1; + const TEXT_TYPE = 0; + const BLOCK_START_TYPE = 1; + const VAR_START_TYPE = 2; + const BLOCK_END_TYPE = 3; + const VAR_END_TYPE = 4; + const NAME_TYPE = 5; + const NUMBER_TYPE = 6; + const STRING_TYPE = 7; + const OPERATOR_TYPE = 8; + const PUNCTUATION_TYPE = 9; + + /** + * Constructor. + * + * @param integer $type The type of the token + * @param string $value The token value + * @param integer $lineno The line positionl in the source + */ + public function __construct($type, $value, $lineno) + { + $this->type = $type; + $this->value = $value; + $this->lineno = $lineno; + } + + /** + * Returns a string representation of the token. + * + * @return string A string representation of the token + */ + public function __toString() + { + return sprintf('%s(%s)', self::typeToString($this->type, true, $this->lineno), $this->value); + } + + /** + * Tests the current token for a type. + * + * The first argument is the type + * of the token (if not given Twig_Token::NAME_TYPE), the second the + * value of the token (if not given value is not checked). + * + * The token value can be an array if multiple checks should be + * performed. + * + * @param integer $type The type to test + * @param array|string|null $values The token value + * + * @return Boolean + */ + public function test($type, $values = null) + { + if (null === $values && !is_int($type)) { + $values = $type; + $type = self::NAME_TYPE; + } + + return ($this->type === $type) && ( + null === $values || + (is_array($values) && in_array($this->value, $values)) || + $this->value == $values + ); + } + + /** + * Gets the line. + * + * @return integer The source line + */ + public function getLine() + { + return $this->lineno; + } + + /** + * Gets the token type. + * + * @return integer The token type + */ + public function getType() + { + return $this->type; + } + + /** + * Gets the token value. + * + * @return string The token value + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the constant representation (internal) of a given type. + * + * @param integer $type The type as an integer + * @param Boolean $short Whether to return a short representation or not + * + * @return string The string representation + */ + static public function typeToString($type, $short = false, $line = -1) + { + switch ($type) { + case self::EOF_TYPE: + $name = 'EOF_TYPE'; + break; + case self::TEXT_TYPE: + $name = 'TEXT_TYPE'; + break; + case self::BLOCK_START_TYPE: + $name = 'BLOCK_START_TYPE'; + break; + case self::VAR_START_TYPE: + $name = 'VAR_START_TYPE'; + break; + case self::BLOCK_END_TYPE: + $name = 'BLOCK_END_TYPE'; + break; + case self::VAR_END_TYPE: + $name = 'VAR_END_TYPE'; + break; + case self::NAME_TYPE: + $name = 'NAME_TYPE'; + break; + case self::NUMBER_TYPE: + $name = 'NUMBER_TYPE'; + break; + case self::STRING_TYPE: + $name = 'STRING_TYPE'; + break; + case self::OPERATOR_TYPE: + $name = 'OPERATOR_TYPE'; + break; + case self::PUNCTUATION_TYPE: + $name = 'PUNCTUATION_TYPE'; + break; + default: + throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line); + } + + return $short ? $name : 'Twig_Token::'.$name; + } + + /** + * Returns the english representation of a given type. + * + * @param integer $type The type as an integer + * @param Boolean $short Whether to return a short representation or not + * + * @return string The string representation + */ + static public function typeToEnglish($type, $line = -1) + { + switch ($type) { + case self::EOF_TYPE: + return 'end of template'; + case self::TEXT_TYPE: + return 'text'; + case self::BLOCK_START_TYPE: + return 'begin of statement block'; + case self::VAR_START_TYPE: + return 'begin of print statement'; + case self::BLOCK_END_TYPE: + return 'end of statement block'; + case self::VAR_END_TYPE: + return 'end of print statement'; + case self::NAME_TYPE: + return 'name'; + case self::NUMBER_TYPE: + return 'number'; + case self::STRING_TYPE: + return 'string'; + case self::OPERATOR_TYPE: + return 'operator'; + case self::PUNCTUATION_TYPE: + return 'punctuation'; + default: + throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line); + } + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser.php new file mode 100755 index 0000000..4245a08 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser.php @@ -0,0 +1,31 @@ + + */ +abstract class Twig_TokenParser implements Twig_TokenParserInterface +{ + protected $parser; + + /** + * Sets the parser associated with this token parser + * + * @param $parser A Twig_Parser instance + */ + public function setParser(Twig_Parser $parser) + { + $this->parser = $parser; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/AutoEscape.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/AutoEscape.php new file mode 100755 index 0000000..324ee60 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/AutoEscape.php @@ -0,0 +1,58 @@ +getLine(); + $value = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(); + if (!in_array($value, array('true', 'false'))) { + throw new Twig_Error_Syntax("Autoescape value must be 'true' or 'false'", $lineno); + } + $value = 'true' === $value ? 'html' : false; + + if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE)) { + if (false === $value) { + throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $lineno); + } + + $value = $this->parser->getStream()->next()->getValue(); + } + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag()); + } + + public function decideBlockEnd($token) + { + return $token->test('endautoescape'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'autoescape'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Block.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Block.php new file mode 100755 index 0000000..5ff104d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Block.php @@ -0,0 +1,72 @@ +getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + if ($this->parser->hasBlock($name)) { + throw new Twig_Error_Syntax("The block '$name' has already been defined", $lineno); + } + $this->parser->pushLocalScope(); + $this->parser->pushBlockStack($name); + + if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { + $stream->next(); + + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + if ($stream->test(Twig_Token::NAME_TYPE)) { + $value = $stream->next()->getValue(); + + if ($value != $name) { + throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $lineno); + } + } + } else { + $body = new Twig_Node(array( + new Twig_Node_Print($this->parser->getExpressionParser()->parseExpression(), $lineno), + )); + } + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $block = new Twig_Node_Block($name, $body, $lineno); + $this->parser->setBlock($name, $block); + $this->parser->popBlockStack(); + $this->parser->popLocalScope(); + + return new Twig_Node_BlockReference($name, $lineno, $this->getTag()); + } + + public function decideBlockEnd($token) + { + return $token->test('endblock'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'block'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Extends.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Extends.php new file mode 100755 index 0000000..8115aa7 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Extends.php @@ -0,0 +1,42 @@ +parser->getParent()) { + throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine()); + } + $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return null; + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'extends'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Filter.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Filter.php new file mode 100755 index 0000000..4ef8023 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Filter.php @@ -0,0 +1,51 @@ +parser->getVarName(); + $ref = new Twig_Node_Expression_BlockReference(new Twig_Node_Expression_Constant($name, $token->getLine()), $token->getLine(), $this->getTag()); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + $block = new Twig_Node_Block($name, $body, $token->getLine()); + $this->parser->setBlock($name, $block); + + return new Twig_Node_Print($filter, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd($token) + { + return $token->test('endfilter'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'filter'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/For.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/For.php new file mode 100755 index 0000000..8364362 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/For.php @@ -0,0 +1,68 @@ +getLine(); + $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, 'in'); + $seq = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideForFork')); + if ($this->parser->getStream()->next()->getValue() == 'else') { + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $else = $this->parser->subparse(array($this, 'decideForEnd'), true); + } else { + $else = null; + } + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + if (count($targets) > 1) { + $keyTarget = $targets->getNode(0); + $valueTarget = $targets->getNode(1); + } else { + $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno); + $valueTarget = $targets->getNode(0); + } + + return new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, $lineno, $this->getTag()); + } + + public function decideForFork($token) + { + return $token->test(array('else', 'endfor')); + } + + public function decideForEnd($token) + { + return $token->test('endfor'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'for'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/From.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/From.php new file mode 100755 index 0000000..b26a4fb --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/From.php @@ -0,0 +1,66 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect('import'); + + $targets = array(); + do { + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + + $alias = $name; + if ($stream->test('as')) { + $stream->next(); + + $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + } + + $targets[$name] = $alias; + + if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) { + break; + } + + $stream->next(); + } while (true); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag()); + + foreach($targets as $name => $alias) { + $this->parser->addImportedFunction($alias, $name, $node->getNode('var')); + } + + return $node; + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'from'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/If.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/If.php new file mode 100755 index 0000000..1621c13 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/If.php @@ -0,0 +1,79 @@ +getLine(); + $expr = $this->parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideIfFork')); + $tests = array($expr, $body); + $else = null; + + $end = false; + while (!$end) { + switch ($this->parser->getStream()->next()->getValue()) { + case 'else': + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $else = $this->parser->subparse(array($this, 'decideIfEnd')); + break; + + case 'elseif': + $expr = $this->parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideIfFork')); + $tests[] = $expr; + $tests[] = $body; + break; + + case 'endif': + $end = true; + break; + + default: + throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), -1); + } + } + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag()); + } + + public function decideIfFork($token) + { + return $token->test(array('elseif', 'else', 'endif')); + } + + public function decideIfEnd($token) + { + return $token->test(array('endif')); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'if'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Import.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Import.php new file mode 100755 index 0000000..e791138 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Import.php @@ -0,0 +1,39 @@ +parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect('as'); + $var = new Twig_Node_Expression_AssignName($this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'import'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Include.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Include.php new file mode 100755 index 0000000..cd44803 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Include.php @@ -0,0 +1,53 @@ +parser->getExpressionParser()->parseExpression(); + + $variables = null; + if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'with')) { + $this->parser->getStream()->next(); + + $variables = $this->parser->getExpressionParser()->parseExpression(); + } + + $only = false; + if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'only')) { + $this->parser->getStream()->next(); + + $only = true; + } + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Include($expr, $variables, $only, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'include'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Macro.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Macro.php new file mode 100755 index 0000000..a6e4733 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Macro.php @@ -0,0 +1,52 @@ +getLine(); + $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(); + + $arguments = $this->parser->getExpressionParser()->parseArguments(); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $this->parser->pushLocalScope(); + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $this->parser->popLocalScope(); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + $this->parser->setMacro($name, new Twig_Node_Macro($name, $body, $arguments, $lineno, $this->getTag())); + + return null; + } + + public function decideBlockEnd($token) + { + return $token->test('endmacro'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'macro'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Sandbox.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Sandbox.php new file mode 100755 index 0000000..a4b6f33 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Sandbox.php @@ -0,0 +1,43 @@ +parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd($token) + { + return $token->test('endsandbox'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'sandbox'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Set.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Set.php new file mode 100755 index 0000000..0cc1960 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Set.php @@ -0,0 +1,66 @@ +getLine(); + $stream = $this->parser->getStream(); + $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); + + $capture = false; + if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) { + $stream->next(); + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + if (count($names) !== count($values)) { + throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignements.", $lineno); + } + } else { + $capture = true; + + if (count($names) > 1) { + throw new Twig_Error_Syntax("When using set with a block, you cannot have a multi-target.", $lineno); + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $values = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } + + return new Twig_Node_Set($capture, $names, $values, $lineno, $this->getTag()); + } + + public function decideBlockEnd($token) + { + return $token->test('endset'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'set'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Spaceless.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Spaceless.php new file mode 100755 index 0000000..bd533c2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParser/Spaceless.php @@ -0,0 +1,45 @@ +getLine(); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideSpacelessEnd'), true); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Spaceless($body, $lineno, $this->getTag()); + } + + public function decideSpacelessEnd($token) + { + return $token->test('endspaceless'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + public function getTag() + { + return 'spaceless'; + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBroker.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBroker.php new file mode 100755 index 0000000..9434c7b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBroker.php @@ -0,0 +1,107 @@ + + */ +class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface +{ + protected $parser; + protected $parsers = array(); + protected $brokers = array(); + + /** + * Constructor. + * + * @param array|Iterable $parsers An Iterable of Twig_TokenParserInterface instances + * @param array|Iterable $brokers An Iterable of Twig_TokenParserBrokerInterface instances + */ + public function __construct($parsers = array(), $brokers = array()) + { + foreach($parsers as $parser) { + if (!$parser instanceof Twig_TokenParserInterface) { + throw new Twig_Error('$parsers must a an array of Twig_TokenParserInterface'); + } + $this->parsers[$parser->getTag()] = $parser; + } + foreach($brokers as $broker) { + if (!$broker instanceof Twig_TokenParserBrokerInterface) { + throw new Twig_Error('$brokers must a an array of Twig_TokenParserBrokerInterface'); + } + $this->brokers[] = $broker; + } + } + + /** + * Adds a TokenParser. + * + * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance + */ + public function addTokenParser(Twig_TokenParserInterface $parser) + { + $this->parsers[$parser->getTag()] = $parser; + } + + /** + * Adds a TokenParserBroker. + * + * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance + */ + public function addTokenParserBroker(Twig_TokenParserBroker $broker) + { + $this->brokers[] = $broker; + } + + /** + * Gets a suitable TokenParser for a tag. + * + * First looks in parsers, then in brokers. + * + * @param string $tag A tag name + * + * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found + */ + public function getTokenParser($tag) + { + if (isset($this->parsers[$tag])) { + return $this->parsers[$tag]; + } + $broker = end($this->brokers); + while (false !== $broker) { + $parser = $broker->getTokenParser($tag); + if (null !== $parser) { + return $parser; + } + $broker = prev($this->brokers); + } + return null; + } + + public function getParser() + { + return $this->parser; + } + + public function setParser(Twig_ParserInterface $parser) + { + $this->parser = $parser; + foreach($this->parsers as $tokenParser) { + $tokenParser->setParser($parser); + } + foreach($this->brokers as $broker) { + $broker->setParser($parser); + } + } +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBrokerInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBrokerInterface.php new file mode 100755 index 0000000..09fee54 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserBrokerInterface.php @@ -0,0 +1,45 @@ + + */ +interface Twig_TokenParserBrokerInterface +{ + /** + * Gets a TokenParser suitable for a tag. + * + * @param string $tag A tag name + * + * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found + */ + function getTokenParser($tag); + + /** + * Calls Twig_TokenParserInterface::setParser on all parsers the implementation knowns of. + * + * @param Twig_ParserInterface $parser A Twig_ParserInterface interface + */ + function setParser(Twig_ParserInterface $parser); + + /** + * Gets the Twig_ParserInterface. + * + * @return null|Twig_ParserInterface A Twig_ParserInterface instance of null + */ + function getParser(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserInterface.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserInterface.php new file mode 100755 index 0000000..142adec --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenParserInterface.php @@ -0,0 +1,42 @@ + + */ +interface Twig_TokenParserInterface +{ + /** + * Sets the parser associated with this token parser + * + * @param $parser A Twig_Parser instance + */ + function setParser(Twig_Parser $parser); + + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + function parse(Twig_Token $token); + + /** + * Gets the tag name associated with this token parser. + * + * @param string The tag name + */ + function getTag(); +} diff --git a/lib/Twig-1.0.0-RC1/lib/Twig/TokenStream.php b/lib/Twig-1.0.0-RC1/lib/Twig/TokenStream.php new file mode 100755 index 0000000..68fe09a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/lib/Twig/TokenStream.php @@ -0,0 +1,139 @@ + + */ +class Twig_TokenStream +{ + protected $tokens; + protected $current; + protected $filename; + + /** + * Constructor. + * + * @param array $tokens An array of tokens + * @param string $filename The name of the filename which tokens are associated with + */ + public function __construct(array $tokens, $filename = null) + { + $this->tokens = $tokens; + $this->current = 0; + $this->filename = $filename; + } + + /** + * Returns a string representation of the token stream. + * + * @return string + */ + public function __toString() + { + return implode("\n", $this->tokens); + } + + /** + * Sets the pointer to the next token and returns the old one. + * + * @return Twig_Token + */ + public function next() + { + if (!isset($this->tokens[++$this->current])) { + throw new Twig_Error_Syntax('Unexpected end of template'); + } + + return $this->tokens[$this->current - 1]; + } + + /** + * Tests a token and returns it or throws a syntax error. + * + * @return Twig_Token + */ + public function expect($type, $value = null, $message = null) + { + $token = $this->tokens[$this->current]; + if (!$token->test($type, $value)) { + $line = $token->getLine(); + throw new Twig_Error_Syntax(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', + $message ? $message.'. ' : '', + Twig_Token::typeToEnglish($token->getType(), $line), $token->getValue(), + Twig_Token::typeToEnglish($type, $line), $value ? sprintf(' with value "%s"', $value) : ''), + $line + ); + } + $this->next(); + + return $token; + } + + /** + * Looks at the next token. + * + * @param integer $number + * + * @return Twig_Token + */ + public function look($number = 1) + { + if (!isset($this->tokens[$this->current + $number])) { + throw new Twig_Error_Syntax('Unexpected end of template'); + } + + return $this->tokens[$this->current + $number]; + } + + /** + * Tests the current token + * + * @return bool + */ + public function test($primary, $secondary = null) + { + return $this->tokens[$this->current]->test($primary, $secondary); + } + + /** + * Checks if end of stream was reached + * + * @return bool + */ + public function isEOF() + { + return $this->tokens[$this->current]->getType() === Twig_Token::EOF_TYPE; + } + + /** + * Gets the current token + * + * @return Twig_Token + */ + public function getCurrent() + { + return $this->tokens[$this->current]; + } + + /** + * Gets the filename associated with this stream + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } +} diff --git a/lib/Twig-1.0.0-RC1/package.xml.tpl b/lib/Twig-1.0.0-RC1/package.xml.tpl new file mode 100755 index 0000000..c0488fb --- /dev/null +++ b/lib/Twig-1.0.0-RC1/package.xml.tpl @@ -0,0 +1,63 @@ + + + Twig + pear.twig-project.org + Twig is a PHP template engine. + + Twig is a template language for PHP, released under the new BSD license + (code and documentation). + + Twig uses a syntax similar to the Django and Jinja template languages which + inspired the Twig runtime environment. + + + Fabien Potencier + fabpot + fabien.potencier@symfony-project.org + yes + + + Armin Ronacher + armin + armin.ronacher@active-4.com + no + + {{ date }} + + + {{ version }} + {{ api_version }} + + + {{ stability }} + {{ stability }} + + BSD Style + - + + + + + + + +{{ files }} + + + + + + + + 5.2.4 + + + 1.4.0 + + + + + diff --git a/lib/Twig-1.0.0-RC1/phpunit.xml b/lib/Twig-1.0.0-RC1/phpunit.xml new file mode 100755 index 0000000..693dec4 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/phpunit.xml @@ -0,0 +1,25 @@ + + + + + + ./test/Twig/ + + + + + + ./lib/Twig/ + + + diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/AutoloaderTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/AutoloaderTest.php new file mode 100755 index 0000000..c8b7999 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/AutoloaderTest.php @@ -0,0 +1,21 @@ +assertFalse(class_exists('FooBarFoo'), '->autoload() does not try to load classes that does not begin with Twig'); + + $autoloader = new Twig_Autoloader(); + $this->assertNull($autoloader->autoload('Foo'), '->autoload() returns false if it is not able to load a class'); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/ExpressionParserTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/ExpressionParserTest.php new file mode 100755 index 0000000..4e43652 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/ExpressionParserTest.php @@ -0,0 +1,124 @@ + false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize($template, 'index')); + } + + public function getFailingTestsForAssignment() + { + return array( + array('{% set false = "foo" %}'), + array('{% set true = "foo" %}'), + array('{% set none = "foo" %}'), + array('{% set 3 = "foo" %}'), + array('{% set 1 + 2 = "foo" %}'), + array('{% set "bar" = "foo" %}'), + array('{% set %}{% endset %}') + ); + } + + /** + * @dataProvider getTestsForArray + */ + public function testArrayExpression($template, $expected) + { + $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false)); + $stream = $env->tokenize($template, 'index'); + $parser = new Twig_Parser($env); + + $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode('expr')); + } + + /** + * @expectedException Twig_Error_Syntax + * @dataProvider getFailingTestsForArray + */ + public function testArraySyntaxError($template) + { + $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize($template, 'index')); + } + + public function getFailingTestsForArray() + { + return array( + array('{{ [1, "a": "b"] }}'), + array('{{ {a: "b"} }}'), + array('{{ {"a": "b", 2} }}'), + ); + } + + public function getTestsForArray() + { + return array( + // simple array + array('{{ [1, 2] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + ), + + // array with trailing , + array('{{ [1, 2, ] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + ), + + // simple hash + array('{{ {"a": "b", "b": "c"} }}', new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Constant('b', 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + + // hash with trailing , + array('{{ {"a": "b", "b": "c", } }}', new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Constant('b', 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + + // hash in an array + array('{{ [1, {"a": "b", "b": "c"}] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Constant('b', 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), 1), + ), + + // array in a hash + array('{{ {"a": [1, 2], "b": "c"} }}', new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Extension/SandboxTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Extension/SandboxTest.php new file mode 100755 index 0000000..f76e72f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Extension/SandboxTest.php @@ -0,0 +1,168 @@ + 'Fabien', + 'obj' => new Object(), + 'arr' => array('obj' => new Object()), + ); + + self::$templates = array( + '1_basic1' => '{{ obj.foo }}', + '1_basic2' => '{{ name|upper }}', + '1_basic3' => '{% if name %}foo{% endif %}', + '1_basic4' => '{{ obj.bar }}', + '1_basic5' => '{{ obj }}', + '1_basic6' => '{{ arr.obj }}', + '1_basic7' => '{{ cycle(["foo","bar"], 1) }}', + '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', + ); + } + + public function testSandboxGloballySet() + { + $twig = $this->getEnvironment(false, array(), self::$templates); + $this->assertEquals('FOO', $twig->loadTemplate('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally'); + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic1')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed method is called'); + } catch (Twig_Sandbox_SecurityError $e) { + } + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic2')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called'); + } catch (Twig_Sandbox_SecurityError $e) { + } + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic3')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + } + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic4')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + } + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic5')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + } + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic6')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + } + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic7')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + } + + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('Object' => 'foo')); + $this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods'); + + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('Object' => '__toString')); + $this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allow some methods'); + + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array('upper')); + $this->assertEquals('FABIEN', $twig->loadTemplate('1_basic2')->render(self::$params), 'Sandbox allow some filters'); + + $twig = $this->getEnvironment(true, array(), self::$templates, array('if')); + $this->assertEquals('foo', $twig->loadTemplate('1_basic3')->render(self::$params), 'Sandbox allow some tags'); + + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('Object' => 'bar')); + $this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties'); + + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('cycle')); + $this->assertEquals('bar', $twig->loadTemplate('1_basic7')->render(self::$params), 'Sandbox allow some functions'); + + } + + public function testSandboxLocallySetForAnInclude() + { + self::$templates = array( + '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}', + '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', + ); + + $twig = $this->getEnvironment(false, array(), self::$templates); + $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include'); + + self::$templates = array( + '3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}', + '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', + ); + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('3_basic')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed'); + } catch (Twig_Sandbox_SecurityError $e) { + } + } + + public function testMacrosInASandbox() + { + $twig = $this->getEnvironment(true, array('autoescape' => true), array('index' => <<{{ text }}

    {% endmacro %} +{{ _self.test('username') }} +EOF + ), array('macro'), array('escape')); + + $this->assertEquals('

    username

    ', $twig->loadTemplate('index')->render(array())); + } + + protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array(), $functions = array()) + { + $loader = new Twig_Loader_Array($templates); + $twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options)); + $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions); + $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed)); + + return $twig; + } +} + +class Object +{ + public $bar = 'bar'; + + public function __toString() + { + return 'foo'; + } + + public function foo() + { + return 'foo'; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/FileCachingTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/FileCachingTest.php new file mode 100755 index 0000000..dd4a892 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/FileCachingTest.php @@ -0,0 +1,47 @@ +tmpDir = sys_get_temp_dir(); + if (!is_writable($this->tmpDir)) { + $this->markTestSkipped(sprintf('Cannot write to %s, cannot test file caching.', $this->tmpDir)); + } + $this->env = new Twig_Environment(new Twig_Loader_String(), array('cache' => $this->tmpDir)); + parent::setUp(); + } + + function testWritingCacheFiles() + { + $name = 'This is just text.'; + $template = $this->env->loadTemplate($name); + $cacheFileName = $this->env->getCacheFilename($name); + + $this->assertTrue(file_exists($cacheFileName), 'Cache file does not exist.'); + $this->fileName = $cacheFileName; + } + + function testClearingCacheFiles() + { + $name = 'I will be deleted.'; + $template = $this->env->loadTemplate($name); + $cacheFileName = $this->env->getCacheFilename($name); + + $this->assertTrue(file_exists($cacheFileName), 'Cache file does not exist.'); + $this->env->clearCacheFiles(); + $this->assertFalse(file_exists($cacheFileName), 'Cache file was not cleared.'); + } + + function tearDown() + { + if($this->fileName) { + unlink($this->fileName); + } + parent::tearDown(); + } +} \ No newline at end of file diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array.test new file mode 100755 index 0000000..e505ca4 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array.test @@ -0,0 +1,45 @@ +--TEST-- +Twig supports array notation +--TEMPLATE-- +{# empty array #} +{{ []|join(',') }} + +{{ [1, 2]|join(',') }} +{{ ['foo', "bar"]|join(',') }} +{{ {0: 1, 'foo': 'bar'}|join(',') }} +{{ {0: 1, 'foo': 'bar'}|keys|join(',') }} + +{# nested arrays #} +{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %} +{{ a[2]|join(',') }} +{{ a[3]["foo"]|join(',') }} + +{# works even if [] is used inside the array #} +{{ [foo[bar]]|join(',') }} + +{# elements can be any expression #} +{{ ['foo'|upper, bar|upper, bar == foo]|join(',') }} + +{# arrays can have a trailing , like in PHP #} +{{ + [ + 1, + 2, + ]|join(',') +}} +--DATA-- +return array('bar' => 'bar', 'foo' => array('bar' => 'bar')) +--EXPECT-- +1,2 +foo,bar +1,bar +0,foo + +1,2 +bar + +bar + +FOO,BAR, + +1,2 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array_call.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array_call.test new file mode 100755 index 0000000..f3df328 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/array_call.test @@ -0,0 +1,14 @@ +--TEST-- +Twig supports method calls +--TEMPLATE-- +{{ items.foo }} +{{ items['foo'] }} +{{ items[foo] }} +{{ items[items[foo]] }} +--DATA-- +return array('foo' => 'bar', 'items' => array('foo' => 'bar', 'bar' => 'foo')) +--EXPECT-- +bar +bar +foo +bar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/binary.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/binary.test new file mode 100755 index 0000000..f5e6845 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/binary.test @@ -0,0 +1,46 @@ +--TEST-- +Twig supports binary operations (+, -, *, /, ~, %, and, or) +--TEMPLATE-- +{{ 1 + 1 }} +{{ 2 - 1 }} +{{ 2 * 2 }} +{{ 2 / 2 }} +{{ 3 % 2 }} +{{ 1 and 1 }} +{{ 1 and 0 }} +{{ 0 and 1 }} +{{ 0 and 0 }} +{{ 1 or 1 }} +{{ 1 or 0 }} +{{ 0 or 1 }} +{{ 0 or 0 }} +{{ 0 or 1 and 0 }} +{{ 1 or 0 and 1 }} +{{ "foo" ~ "bar" }} +{{ foo ~ "bar" }} +{{ "foo" ~ bar }} +{{ foo ~ bar }} +{{ 20 // 7 }} +--DATA-- +return array('foo' => 'bar', 'bar' => 'foo') +--EXPECT-- +2 +1 +4 +1 +1 +1 + + + +1 +1 +1 + + +1 +foobar +barbar +foofoo +barfoo +2 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/comparison.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/comparison.test new file mode 100755 index 0000000..726b850 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/comparison.test @@ -0,0 +1,14 @@ +--TEST-- +Twig supports comparison operators (==, !=, <, >, >=, <=) +--TEMPLATE-- +{{ 1 > 2 }}/{{ 1 > 1 }}/{{ 1 >= 2 }}/{{ 1 >= 1 }} +{{ 1 < 2 }}/{{ 1 < 1 }}/{{ 1 <= 2 }}/{{ 1 <= 1 }} +{{ 1 == 1 }}/{{ 1 == 2 }} +{{ 1 != 1 }}/{{ 1 != 2 }} +--DATA-- +return array() +--EXPECT-- +///1 +1//1/1 +1/ +/1 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/dotdot.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/dotdot.test new file mode 100755 index 0000000..b3ee396 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/dotdot.test @@ -0,0 +1,17 @@ +--TEST-- +Twig supports the .. operator +--TEMPLATE-- +{% for i in 0..10 %}{{ i }} {% endfor %} + +{% for letter in 'a'..'z' %}{{ letter }} {% endfor %} + +{% for letter in 'a'|upper..'z'|upper %}{{ letter }} {% endfor %} + +{% for i in foo[0]..foo[1] %}{{ i }} {% endfor %} +--DATA-- +return array('foo' => array(1, 10)) +--EXPECT-- +0 1 2 3 4 5 6 7 8 9 10 +a b c d e f g h i j k l m n o p q r s t u v w x y z +A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +1 2 3 4 5 6 7 8 9 10 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/grouping.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/grouping.test new file mode 100755 index 0000000..79f8e0b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/grouping.test @@ -0,0 +1,8 @@ +--TEST-- +Twig supports grouping of expressions +--TEMPLATE-- +{{ (2 + 2) / 2 }} +--DATA-- +return array() +--EXPECT-- +2 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/in.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/in.test new file mode 100755 index 0000000..a5cbfb8 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/in.test @@ -0,0 +1,20 @@ +--TEST-- +Twig supports the in operator +--TEMPLATE-- +{% if bar in foo %} +TRUE +{% endif %} +{% if not (bar in foo) %} +{% else %} +TRUE +{% endif %} +{% if bar not in foo %} +{% else %} +TRUE +{% endif %} +--DATA-- +return array('bar' => 'bar', 'foo' => array('bar' => 'bar')) +--EXPECT-- +TRUE +TRUE +TRUE diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/magic_call.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/magic_call.test new file mode 100755 index 0000000..159db96 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/magic_call.test @@ -0,0 +1,27 @@ +--TEST-- +Twig supports __call() for attributes +--TEMPLATE-- +{{ foo.foo }} +{{ foo.bar }} +--DATA-- +class TestClassForMagicCallAttributes +{ + public function getBar() + { + return 'bar_from_getbar'; + } + + public function __call($method, $arguments) + { + if ('foo' === $method) + { + return 'foo_from_call'; + } + + return false; + } +} +return array('foo' => new TestClassForMagicCallAttributes()) +--EXPECT-- +foo_from_call +bar_from_getbar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/method_call.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/method_call.test new file mode 100755 index 0000000..0957d18 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/method_call.test @@ -0,0 +1,26 @@ +--TEST-- +Twig supports method calls +--TEMPLATE-- +{{ items.foo.foo }} +{{ items.foo.getFoo() }} +{{ items.foo.bar }} +{{ items.foo['bar'] }} +{{ items.foo.bar('a', 43) }} +{{ items.foo.bar(foo) }} +{{ items.foo.self.foo() }} +{{ items.foo.is }} +{{ items.foo.in }} +{{ items.foo.not }} +--DATA-- +return array('foo' => 'bar', 'items' => array('foo' => new Foo(), 'bar' => 'foo')) +--EXPECT-- +foo +foo +bar + +bar_a-43 +bar_bar +foo +is +in +not diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/postfix.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/postfix.test new file mode 100755 index 0000000..db047c0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/postfix.test @@ -0,0 +1,21 @@ +--TEST-- +Twig parses postfix expressions +--TEMPLATE-- + +{% macro foo() %}foo{% endmacro %} + +{{ 'a' }} +{{ 'a'|upper }} +{{ ('a')|upper }} +{{ -1|upper }} +{{ _self.foo() }} +{{ (_self).foo() }} +--DATA-- +return array(); +--EXPECT-- +a +A +A +-1 +foo +foo diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/ternary_operator.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/ternary_operator.test new file mode 100755 index 0000000..d070824 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/ternary_operator.test @@ -0,0 +1,16 @@ +--TEST-- +Twig supports the ternary operator +--TEMPLATE-- +{{ 1 ? 'YES' : 'NO' }} +{{ 0 ? 'YES' : 'NO' }} +{{ 0 ? 'YES' : (1 ? 'YES1' : 'NO1') }} +{{ 0 ? 'YES' : (0 ? 'YES1' : 'NO1') }} +{{ 1 == 1 ? 'foo
    ':'' }} +--DATA-- +return array() +--EXPECT-- +YES +NO +YES1 +NO1 +foo
    diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/unary.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/unary.test new file mode 100755 index 0000000..b79219a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/expressions/unary.test @@ -0,0 +1,12 @@ +--TEST-- +Twig supports unary operators (not, -, +) +--TEMPLATE-- +{{ not 1 }}/{{ not 0 }} +{{ +1 + 1 }}/{{ -1 - 1 }} +{{ not (false or true) }} +--DATA-- +return array() +--EXPECT-- +/1 +2/-2 + diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/date.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/date.test new file mode 100755 index 0000000..21b57ba --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/date.test @@ -0,0 +1,23 @@ +--TEST-- +"date" filter +--TEMPLATE-- +{{ date1|date }} +{{ date1|date('d/m/Y') }} +{{ date2|date }} +{{ date2|date('d/m/Y') }} +{{ date3|date }} +{{ date3|date('d/m/Y') }} +{{ date4|date }} +{{ date4|date('d/m/Y') }} +--DATA-- +date_default_timezone_set('UTC'); +return array('date1' => mktime(13, 45, 0, 10, 4, 2010), 'date2' => new DateTime('2010-10-04 13:45'), 'date3' => '2010-10-04 13:45', 'date4' => 1286199900) +--EXPECT-- +October 4, 2010 13:45 +04/10/2010 +October 4, 2010 13:45 +04/10/2010 +October 4, 2010 13:45 +04/10/2010 +October 4, 2010 13:45 +04/10/2010 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/default.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/default.test new file mode 100755 index 0000000..ad1a338 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/default.test @@ -0,0 +1,14 @@ +--TEST-- +"default" filter +--TEMPLATE-- +{{ foo|default('bar') }} +{{ bar|default('foo') }} +{{ not_defined|default('bar') }} +{{ 'a' ~ foo|default('b') }} +--DATA-- +return array('foo' => null, 'bar' => 'bar') +--EXPECT-- +bar +bar +bar +ab diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/format.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/format.test new file mode 100755 index 0000000..97221ff --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/format.test @@ -0,0 +1,8 @@ +--TEST-- +"format" filter +--TEMPLATE-- +{{ string|format(foo, 3) }} +--DATA-- +return array('string' => '%s/%d', 'foo' => 'bar') +--EXPECT-- +bar/3 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/length.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/length.test new file mode 100755 index 0000000..b5ef879 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/length.test @@ -0,0 +1,10 @@ +--TEST-- +"length" filter +--TEMPLATE-- +{{ array|length }} +{{ string|length }} +--DATA-- +return array('array' => array(1, 4), 'string' => 'foo') +--EXPECT-- +2 +3 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/merge.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/merge.test new file mode 100755 index 0000000..357b352 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/merge.test @@ -0,0 +1,14 @@ +--TEST-- +"merge" filter +--TEMPLATE-- +{{ items|merge({'bar': 'foo'})|join }} +{{ items|merge({'bar': 'foo'})|keys|join }} +{{ {'bar': 'foo'}|merge(items)|join }} +{{ {'bar': 'foo'}|merge(items)|keys|join }} +--DATA-- +return array('items' => array('foo' => 'bar')) +--EXPECT-- +barfoo +foobar +foobar +barfoo diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/sort.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/sort.test new file mode 100755 index 0000000..21d575f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/filters/sort.test @@ -0,0 +1,10 @@ +--TEST-- +"sort" filter +--TEMPLATE-- +{{ array1|sort|join }} +{{ array2|sort|join }} +--DATA-- +return array('array1' => array(4, 1), 'array2' => array('foo', 'bar')) +--EXPECT-- +14 +barfoo diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/constant.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/constant.test new file mode 100755 index 0000000..dbff4ab --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/constant.test @@ -0,0 +1,12 @@ +--TEST-- +"constant" filter +--TEMPLATE-- +{{ date|date(constant('DATE_W3C')) }} +--DATA-- +$d = date_default_timezone_get(); +date_default_timezone_set('UTC'); +$ret = array('date' => strtotime('2005-10-15 10:00:01')); +date_default_timezone_set($d); +return $ret; +--EXPECT-- +2005-10-15T10:00:01+00:00 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/cycle.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/cycle.test new file mode 100755 index 0000000..522a63b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/functions/cycle.test @@ -0,0 +1,16 @@ +--TEST-- +"cycle" function +--TEMPLATE-- +{% for i in 0..6 %} +{{ cycle(array1, i) }}-{{ cycle(array2, i) }} +{% endfor %} +--DATA-- +return array('array1' => array('odd', 'even'), 'array2' => array('apple', 'orange', 'citrus')) +--EXPECT-- +odd-apple +even-orange +odd-citrus +even-apple +odd-orange +even-citrus +odd-apple diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/basic.test new file mode 100755 index 0000000..62d8c3c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/basic.test @@ -0,0 +1,22 @@ +--TEST-- +"autoescape" tag applies escaping on its children +--TEMPLATE-- +{% autoescape true %} +{{ var }}
    +{% endautoescape %} +{% autoescape false %} +{{ var }}
    +{% endautoescape %} +{% autoescape true %} +{{ var }}
    +{% endautoescape %} +{% autoescape false %} +{{ var }}
    +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br />
    +

    +<br />
    +

    diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test new file mode 100755 index 0000000..b48f73e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test @@ -0,0 +1,12 @@ +--TEST-- +"autoescape" tag applies escaping on embedded blocks +--TEMPLATE-- +{% autoescape true %} + {% block foo %} + {{ var }} + {% endblock %} +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br /> diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test new file mode 100755 index 0000000..fd62a84 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test @@ -0,0 +1,10 @@ +--TEST-- +"autoescape" tag does not double-escape +--TEMPLATE-- +{% autoescape true %} +{{ var|escape }} +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br /> diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/functions.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/functions.test new file mode 100755 index 0000000..9a229d0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/functions.test @@ -0,0 +1,83 @@ +--TEST-- +"autoescape" tag applies escaping after calling functions +--TEMPLATE-- + +autoescape false +{% autoescape false %} + +safe_br +{{ safe_br() }} + +unsafe_br +{{ unsafe_br() }} + +{% endautoescape %} + +autoescape true +{% autoescape true %} + +safe_br +{{ safe_br() }} + +unsafe_br +{{ unsafe_br() }} + +unsafe_br()|raw +{{ (unsafe_br())|raw }} + +safe_br()|escape +{{ (safe_br())|escape }} + +safe_br()|raw +{{ (safe_br())|raw }} + +unsafe_br()|escape +{{ (unsafe_br())|escape }} + +{% endautoescape %} + +autoescape true js +{% autoescape true js %} + +safe_br +{{ safe_br() }} + +{% endautoescape %} +--DATA-- +return array() +--EXPECT-- + +autoescape false + +safe_br +
    + +unsafe_br +
    + + +autoescape true + +safe_br +
    + +unsafe_br +<br /> + +unsafe_br()|raw +
    + +safe_br()|escape +<br /> + +safe_br()|raw +
    + +unsafe_br()|escape +<br /> + + +autoescape true js + +safe_br +\x3cbr \x2f\x3e diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/literal.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/literal.test new file mode 100755 index 0000000..4c92d08 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/literal.test @@ -0,0 +1,45 @@ +--TEST-- +"autoescape" tag does not apply escaping on literals +--TEMPLATE-- +{% autoescape true %} + +1. Simple literal +{{ "
    " }} + +2. Conditional expression with only literals +{{ true ? "
    " : "
    " }} + +3. Conditonal expression with a variable +{{ true ? "
    " : someVar }} + +4. Nested conditionals with only literals +{{ true ? (true ? "
    " : "
    ") : "\n" }} + +5. Nested conditionals with a variable +{{ true ? (true ? "
    " : someVar) : "\n" }} + +6. Nested conditionals with a variable marked safe +{{ true ? (true ? "
    " : someVar|raw) : "\n" }} + +{% endautoescape %} +--DATA-- +return array() +--EXPECT-- + +1. Simple literal +
    + +2. Conditional expression with only literals +
    + +3. Conditonal expression with a variable +<br /> + +4. Nested conditionals with only literals +
    + +5. Nested conditionals with a variable +<br /> + +6. Nested conditionals with a variable marked safe +
    diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/nested.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/nested.test new file mode 100755 index 0000000..c911211 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/nested.test @@ -0,0 +1,26 @@ +--TEST-- +"autoescape" tags can be nested at will +--TEMPLATE-- +{{ var }} +{% autoescape true %} + {{ var }} + {% autoescape false %} + {{ var }} + {% autoescape true %} + {{ var }} + {% endautoescape %} + {{ var }} + {% endautoescape %} + {{ var }} +{% endautoescape %} +{{ var }} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br /> + <br /> +
    + <br /> +
    + <br /> +<br /> diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/objects.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/objects.test new file mode 100755 index 0000000..f6c03ed --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/objects.test @@ -0,0 +1,26 @@ +--TEST-- +"autoescape" tag applies escaping to object method calls +--TEMPLATE-- +{% autoescape true %} +{{ user.name }} +{{ user.name|lower }} +{{ user }} +{% endautoescape %} +--DATA-- +class UserForAutoEscapeTest +{ + public function getName() + { + return 'Fabien
    '; + } + + public function __toString() + { + return 'Fabien
    '; + } +} +return array('user' => new UserForAutoEscapeTest()) +--EXPECT-- +Fabien<br /> +fabien<br /> +Fabien<br /> diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test new file mode 100755 index 0000000..9ea4fd4 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test @@ -0,0 +1,11 @@ +--TEST-- +"autoescape" tag accepts an escaping strategy +--TEMPLATE-- +{% autoescape true js %}{{ var }}{% endautoescape %} + +{% autoescape true html %}{{ var }}{% endautoescape %} +--DATA-- +return array('var' => '
    "') +--EXPECT-- +\x3cbr \x2f\x3e\x22 +<br />" diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/type.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/type.test new file mode 100755 index 0000000..17cec13 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/type.test @@ -0,0 +1,69 @@ +--TEST-- +escape types +--TEMPLATE-- + +1. autoescape true |escape('js') + +{% autoescape true %} + +{% endautoescape %} + +2. autoescape true html |escape('js') + +{% autoescape true html %} + +{% endautoescape %} + +3. autoescape true js |escape('js') + +{% autoescape true js %} + +{% endautoescape %} + +4. no escape + +{% autoescape false %} + +{% endautoescape %} + +5. |escape('js')|escape('html') + +{% autoescape false %} + +{% endautoescape %} + +6. autoescape true html |escape('js')|escape('html') + +{% autoescape true html %} + +{% endautoescape %} + +--DATA-- +return array('msg' => "<>\n'\"") +--EXPECT-- + +1. autoescape true |escape('js') + + + +2. autoescape true html |escape('js') + + + +3. autoescape true js |escape('js') + + + +4. no escape + + + +5. |escape('js')|escape('html') + + + +6. autoescape true html |escape('js')|escape('html') + + + diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test new file mode 100755 index 0000000..d795b82 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test @@ -0,0 +1,131 @@ +--TEST-- +"autoescape" tag applies escaping after calling filters +--TEMPLATE-- +{% autoescape true %} + +(escape_and_nl2br is an escaper filter) + +1. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped ) +{{ var|escape_and_nl2br }} + +2. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped, |raw is redundant ) +{{ var|escape_and_nl2br|raw }} + +3. Explicit escape +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is explicitly escaped by |escape ) +{{ var|escape_and_nl2br|escape }} + +4. Escape non-escaper filter output +( var is upper-cased by |upper, + the output is auto-escaped ) +{{ var|upper }} + +5. Escape if last filter is not an escaper +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is upper-cased by |upper, + the output is auto-escaped as |upper is not an escaper ) +{{ var|escape_and_nl2br|upper }} + +6. Don't escape escaper filter output +( var is upper cased by upper, + the output is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped as |escape_and_nl2br is an escaper ) +{{ var|upper|escape_and_nl2br }} + +7. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + the output is auto-escaped ) +{{ "%s"|format(var) }} + +8. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + |raw is redundant, + the output is auto-escaped ) +{{ "%s"|raw|format(var) }} + +9. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end ) +{{ "%s"|format(var)|raw }} + +10. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end, + the |raw filter on var is redundant ) +{{ "%s"|format(var|raw)|raw }} + +{% endautoescape %} +--DATA-- +return array('var' => "\nTwig") +--EXPECT-- + +(escape_and_nl2br is an escaper filter) + +1. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped ) +<Fabien>
    +Twig + +2. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped, |raw is redundant ) +<Fabien>
    +Twig + +3. Explicit escape +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is explicitly escaped by |escape ) +&lt;Fabien&gt;<br /> +Twig + +4. Escape non-escaper filter output +( var is upper-cased by |upper, + the output is auto-escaped ) +<FABIEN> +TWIG + +5. Escape if last filter is not an escaper +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is upper-cased by |upper, + the output is auto-escaped as |upper is not an escaper ) +&LT;FABIEN&GT;<BR /> +TWIG + +6. Don't escape escaper filter output +( var is upper cased by upper, + the output is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped as |escape_and_nl2br is an escaper ) +<FABIEN>
    +TWIG + +7. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + the output is auto-escaped ) +<b><Fabien> +Twig</b> + +8. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + |raw is redundant, + the output is auto-escaped ) +<b><Fabien> +Twig</b> + +9. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end ) + +Twig + +10. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end, + the |raw filter on var is redundant ) + +Twig diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test new file mode 100755 index 0000000..0ff1ad3 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test @@ -0,0 +1,23 @@ +--TEST-- +"autoescape" tag do not applies escaping on filter arguments +--TEMPLATE-- +{% autoescape true %} +{{ var|nl2br("
    ") }} +{{ var|nl2br("
    "|escape) }} +{{ var|nl2br(sep) }} +{{ var|nl2br(sep|raw) }} +{{ var|nl2br(sep|escape) }} +{% endautoescape %} +--DATA-- +return array('var' => "\nTwig", 'sep' => '
    ') +--EXPECT-- +<Fabien>
    +Twig +<Fabien><br /> +Twig +<Fabien>
    +Twig +<Fabien>
    +Twig +<Fabien><br /> +Twig diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test new file mode 100755 index 0000000..44d42e7 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test @@ -0,0 +1,68 @@ +--TEST-- +"autoescape" tag applies escaping after calling filters, and before calling pre_escape filters +--TEMPLATE-- +{% autoescape true %} + +(nl2br is pre_escaped for "html" and declared safe for "html") + +1. Pre-escape and don't post-escape +( var|escape|nl2br ) +{{ var|nl2br }} + +2. Don't double-pre-escape +( var|escape|nl2br ) +{{ var|escape|nl2br }} + +3. Don't escape safe values +( var|raw|nl2br ) +{{ var|raw|nl2br }} + +4. Don't escape safe values +( var|escape|nl2br|nl2br ) +{{ var|nl2br|nl2br }} + +5. Re-escape values that are escaped for an other contexts +( var|escape_something|escape|nl2br ) +{{ var|escape_something|nl2br }} + +6. Still escape when using filters not declared safe +( var|escape|nl2br|upper|escape ) +{{ var|nl2br|upper }} + +{% endautoescape %} +--DATA-- +return array('var' => "\nTwig") +--EXPECT-- + +(nl2br is pre_escaped for "html" and declared safe for "html") + +1. Pre-escape and don't post-escape +( var|escape|nl2br ) +<Fabien>
    +Twig + +2. Don't double-pre-escape +( var|escape|nl2br ) +<Fabien>
    +Twig + +3. Don't escape safe values +( var|raw|nl2br ) +
    +Twig + +4. Don't escape safe values +( var|escape|nl2br|nl2br ) +<Fabien>

    +Twig + +5. Re-escape values that are escaped for an other contexts +( var|escape_something|escape|nl2br ) +<FABIEN>
    +TWIG + +6. Still escape when using filters not declared safe +( var|escape|nl2br|upper|escape ) +&LT;FABIEN&GT;<BR /> +TWIG + diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/block/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/block/basic.test new file mode 100755 index 0000000..360dcf0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/block/basic.test @@ -0,0 +1,11 @@ +--TEST-- +"block" tag +--TEMPLATE-- +{% block title1 %}FOO{% endblock %} +{% block title2 foo|lower %} +--TEMPLATE(foo.twig)-- +{% block content %}{% endblock %} +--DATA-- +return array('foo' => 'bar') +--EXPECT-- +FOObar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/basic.test new file mode 100755 index 0000000..82094f2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/basic.test @@ -0,0 +1,10 @@ +--TEST-- +"filter" tag applies a filter on its children +--TEMPLATE-- +{% filter upper %} +Some text with a {{ var }} +{% endfilter %} +--DATA-- +return array('var' => 'var') +--EXPECT-- +SOME TEXT WITH A VAR diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/multiple.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/multiple.test new file mode 100755 index 0000000..75512ef --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/multiple.test @@ -0,0 +1,10 @@ +--TEST-- +"filter" tags accept multiple chained filters +--TEMPLATE-- +{% filter lower|title %} + {{ var }} +{% endfilter %} +--DATA-- +return array('var' => 'VAR') +--EXPECT-- + Var diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/nested.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/nested.test new file mode 100755 index 0000000..7e4e4eb --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/nested.test @@ -0,0 +1,16 @@ +--TEST-- +"filter" tags can be nested at will +--TEMPLATE-- +{% filter lower|title %} + {{ var }} + {% filter upper %} + {{ var }} + {% endfilter %} + {{ var }} +{% endfilter %} +--DATA-- +return array('var' => 'var') +--EXPECT-- + Var + Var + Var diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test new file mode 100755 index 0000000..22745ea --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test @@ -0,0 +1,13 @@ +--TEST-- +"filter" tag applies the filter on "for" tags +--TEMPLATE-- +{% filter upper %} +{% for item in items %} +{{ item }} +{% endfor %} +{% endfilter %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- +A +B diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test new file mode 100755 index 0000000..02381dd --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test @@ -0,0 +1,29 @@ +--TEST-- +"filter" tag applies the filter on "if" tags +--TEMPLATE-- +{% filter upper %} +{% if items %} +{{ items|join(', ') }} +{% endif %} + +{% if items.3 %} +FOO +{% else %} +{{ items.1 }} +{% endif %} + +{% if items.3 %} +FOO +{% elseif items.1 %} +{{ items.0 }} +{% endif %} + +{% endfilter %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- +A, B + +B + +A diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/context.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/context.test new file mode 100755 index 0000000..ddc6930 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/context.test @@ -0,0 +1,18 @@ +--TEST-- +"for" tag keeps the context safe +--TEMPLATE-- +{% for item in items %} + {% for item in items %} + * {{ item }} + {% endfor %} + * {{ item }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * a + * b + * a + * a + * b + * b diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/else.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/else.test new file mode 100755 index 0000000..8c6a7d2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/else.test @@ -0,0 +1,21 @@ +--TEST-- +"for" tag can use an "else" clause +--TEMPLATE-- +{% for item in items %} + * {{ item }} +{% else %} + no item +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * a + * b +--DATA-- +return array('items' => array()) +--EXPECT-- + no item +--DATA-- +return array() +--EXPECT-- + no item diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/inner_variables.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/inner_variables.test new file mode 100755 index 0000000..49fb9ca --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/inner_variables.test @@ -0,0 +1,17 @@ +--TEST-- +"for" tag does not reset inner variables +--TEMPLATE-- +{% for i in 1..2 %} + {% for j in 0..2 %} + {{k}}{% set k = k+1 %} {{ loop.parent.loop.index }} + {% endfor %} +{% endfor %} +--DATA-- +return array('k' => 0) +--EXPECT-- + 0 1 + 1 1 + 2 1 + 3 2 + 4 2 + 5 2 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys.test new file mode 100755 index 0000000..4e22cb4 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys.test @@ -0,0 +1,11 @@ +--TEST-- +"for" tag can iterate over keys +--TEMPLATE-- +{% for key in items|keys %} + * {{ key }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * 0 + * 1 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test new file mode 100755 index 0000000..4c21168 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test @@ -0,0 +1,11 @@ +--TEST-- +"for" tag can iterate over keys and values +--TEMPLATE-- +{% for key, item in items %} + * {{ key }}/{{ item }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * 0/a + * 1/b diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context.test new file mode 100755 index 0000000..93bc76a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context.test @@ -0,0 +1,19 @@ +--TEST-- +"for" tag adds a loop variable to the context +--TEMPLATE-- +{% for item in items %} + * {{ loop.index }}/{{ loop.index0 }} + * {{ loop.revindex }}/{{ loop.revindex0 }} + * {{ loop.first }}/{{ loop.last }}/{{ loop.length }} + +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * 1/0 + * 2/1 + * 1//2 + + * 2/1 + * 1/0 + * /1/2 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test new file mode 100755 index 0000000..4ec5440 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test @@ -0,0 +1,10 @@ +--TEST-- +"for" tag adds a loop variable to the context locally +--TEMPLATE-- +{% for item in items %} +{% endfor %} +{% if not loop %}WORKS{% endif %} +--DATA-- +return array('items' => array()) +--EXPECT-- +WORKS diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/nested_else.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/nested_else.test new file mode 100755 index 0000000..f8b9f6b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/nested_else.test @@ -0,0 +1,17 @@ +--TEST-- +"for" tag can use an "else" clause +--TEMPLATE-- +{% for item in items %} + {% for item in items1 %} + * {{ item }} + {% else %} + no {{ item }} + {% endfor %} +{% else %} + no item1 +{% endfor %} +--DATA-- +return array('items' => array('a', 'b'), 'items1' => array()) +--EXPECT-- +no a + no b diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects.test new file mode 100755 index 0000000..5034437 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects.test @@ -0,0 +1,43 @@ +--TEST-- +"for" tag iterates over iterable objects +--TEMPLATE-- +{% for item in items %} + * {{ item }} + * {{ loop.index }}/{{ loop.index0 }} + * {{ loop.first }} + +{% endfor %} + +{% for key, value in items %} + * {{ key }}/{{ value }} +{% endfor %} + +{% for key in items|keys %} + * {{ key }} +{% endfor %} +--DATA-- +class ItemsIterator implements Iterator +{ + protected $values = array('foo' => 'bar', 'bar' => 'foo'); + public function current() { return current($this->values); } + public function key() { return key($this->values); } + public function next() { return next($this->values); } + public function rewind() { return reset($this->values); } + public function valid() { return false !== current($this->values); } +} +return array('items' => new ItemsIterator()) +--EXPECT-- + * bar + * 1/0 + * 1 + + * foo + * 2/1 + * + + + * foo/bar + * bar/foo + + * foo + * bar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects_countable.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects_countable.test new file mode 100755 index 0000000..4a1ff61 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/objects_countable.test @@ -0,0 +1,47 @@ +--TEST-- +"for" tag iterates over iterable and countable objects +--TEMPLATE-- +{% for item in items %} + * {{ item }} + * {{ loop.index }}/{{ loop.index0 }} + * {{ loop.revindex }}/{{ loop.revindex0 }} + * {{ loop.first }}/{{ loop.last }}/{{ loop.length }} + +{% endfor %} + +{% for key, value in items %} + * {{ key }}/{{ value }} +{% endfor %} + +{% for key in items|keys %} + * {{ key }} +{% endfor %} +--DATA-- +class ItemsIteratorCountable implements Iterator, Countable +{ + protected $values = array('foo' => 'bar', 'bar' => 'foo'); + public function current() { return current($this->values); } + public function key() { return key($this->values); } + public function next() { return next($this->values); } + public function rewind() { return reset($this->values); } + public function valid() { return false !== current($this->values); } + public function count() { return count($this->values); } +} +return array('items' => new ItemsIteratorCountable()) +--EXPECT-- + * bar + * 1/0 + * 2/1 + * 1//2 + + * foo + * 2/1 + * 1/0 + * /1/2 + + + * foo/bar + * bar/foo + + * foo + * bar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/recursive.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/recursive.test new file mode 100755 index 0000000..17b2e22 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/recursive.test @@ -0,0 +1,18 @@ +--TEST-- +"for" tags can be nested +--TEMPLATE-- +{% for key, item in items %} +* {{ key }} ({{ loop.length }}): +{% for value in item %} + * {{ value }} ({{ loop.length }}) +{% endfor %} +{% endfor %} +--DATA-- +return array('items' => array('a' => array('a1', 'a2', 'a3'), 'b' => array('b1'))) +--EXPECT-- +* a (2): + * a1 (3) + * a2 (3) + * a3 (3) +* b (2): + * b1 (1) diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/values.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/values.test new file mode 100755 index 0000000..82f2ae8 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/for/values.test @@ -0,0 +1,11 @@ +--TEST-- +"for" tag iterates over item values +--TEMPLATE-- +{% for item in items %} + * {{ item }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * a + * b diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/from.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/from.test new file mode 100755 index 0000000..5f5da0e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/from.test @@ -0,0 +1,14 @@ +--TEST-- +global variables +--TEMPLATE-- +{% include "included.twig" %} +{% from "included.twig" import foobar %} +{{ foobar() }} +--TEMPLATE(included.twig)-- +{% macro foobar() %} +called foobar +{% endmacro %} +--DATA-- +return array(); +--EXPECT-- +called foobar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/basic.test new file mode 100755 index 0000000..2482ddf --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/basic.test @@ -0,0 +1,22 @@ +--TEST-- +"if" creates a condition +--TEMPLATE-- +{% if a %} + {{ a }} +{% elseif b %} + {{ b }} +{% else %} + NOTHING +{% endif %} +--DATA-- +return array('a' => 'a') +--EXPECT-- + a +--DATA-- +return array('b' => 'b') +--EXPECT-- + b +--DATA-- +return array() +--EXPECT-- + NOTHING diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/expression.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/expression.test new file mode 100755 index 0000000..edfb73d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/if/expression.test @@ -0,0 +1,22 @@ +--TEST-- +"if" takes an expression as a test +--TEMPLATE-- +{% if a < 2 %} + A1 +{% elseif a > 10 %} + A2 +{% else %} + A3 +{% endif %} +--DATA-- +return array('a' => 1) +--EXPECT-- + A1 +--DATA-- +return array('a' => 12) +--EXPECT-- + A2 +--DATA-- +return array('a' => 7) +--EXPECT-- + A3 diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/basic.test new file mode 100755 index 0000000..8fe1a6c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/basic.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag +--TEMPLATE-- +FOO +{% include "foo.twig" %} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return array() +--EXPECT-- +FOO + +FOOBAR +BAR diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/expression.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/expression.test new file mode 100755 index 0000000..eaeeb11 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/expression.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag allows expressions for the template to include +--TEMPLATE-- +FOO +{% include foo %} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return array('foo' => 'foo.twig') +--EXPECT-- +FOO + +FOOBAR +BAR diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/only.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/only.test new file mode 100755 index 0000000..22e3d0f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/only.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag accept variables and only +--TEMPLATE-- +{% include "foo.twig" %} +{% include "foo.twig" only %} +{% include "foo.twig" with {'foo1': 'bar'} %} +{% include "foo.twig" with {'foo1': 'bar'} only %} +--TEMPLATE(foo.twig)-- +{% for k, v in _context %}{{ k }},{% endfor %} +--DATA-- +return array('foo' => 'bar') +--EXPECT-- +foo,_parent, +_parent, +foo,foo1,_parent, +foo1,_parent, diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/template_instance.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/template_instance.test new file mode 100755 index 0000000..6ba064a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/template_instance.test @@ -0,0 +1,10 @@ +--TEST-- +"include" tag accepts Twig_Template instance +--TEMPLATE-- +{% include foo %} FOO +--TEMPLATE(foo.twig)-- +BAR +--DATA-- +return array('foo' => $twig->loadTemplate('foo.twig')) +--EXPECT-- +BAR FOO diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/with_variables.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/with_variables.test new file mode 100755 index 0000000..41384ac --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/include/with_variables.test @@ -0,0 +1,12 @@ +--TEST-- +"include" tag accept variables +--TEMPLATE-- +{% include "foo.twig" with {'foo': 'bar'} %} +{% include "foo.twig" with vars %} +--TEMPLATE(foo.twig)-- +{{ foo }} +--DATA-- +return array('vars' => array('foo' => 'bar')) +--EXPECT-- +bar +bar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/basic.test new file mode 100755 index 0000000..0778a4b --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/basic.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "foo.twig" %} + +{% block content %} +FOO +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}{% endblock %} +--DATA-- +return array() +--EXPECT-- +FOO diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test new file mode 100755 index 0000000..8576e77 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends standalone ? foo : 'bar.twig' %} + +{% block content %}{{ parent() }}FOO{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}FOO{% endblock %} +--TEMPLATE(bar.twig)-- +{% block content %}BAR{% endblock %} +--DATA-- +return array('foo' => 'foo.twig', 'standalone' => true) +--EXPECT-- +FOOFOO diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test new file mode 100755 index 0000000..ee06ddc --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends foo %} + +{% block content %} +FOO +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}{% endblock %} +--DATA-- +return array('foo' => 'foo.twig') +--EXPECT-- +FOO diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test new file mode 100755 index 0000000..dfc2b6c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test @@ -0,0 +1,12 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "layout.twig" %}{% block content %}{{ parent() }}index {% endblock %} +--TEMPLATE(layout.twig)-- +{% extends "base.twig" %}{% block content %}{{ parent() }}layout {% endblock %} +--TEMPLATE(base.twig)-- +{% block content %}base {% endblock %} +--DATA-- +return array() +--EXPECT-- +base layout index diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test new file mode 100755 index 0000000..71e3cdf --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test @@ -0,0 +1,16 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "layout.twig" %} +{% block inside %}INSIDE{% endblock inside %} +--TEMPLATE(layout.twig)-- +{% extends "base.twig" %} +{% block body %} + {% block inside '' %} +{% endblock body %} +--TEMPLATE(base.twig)-- +{% block body '' %} +--DATA-- +return array() +--EXPECT-- +INSIDE diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent.test new file mode 100755 index 0000000..4f975db --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent.test @@ -0,0 +1,12 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "foo.twig" %} + +{% block content %}{{ parent() }}FOO{{ parent() }}{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}BAR{% endblock %} +--DATA-- +return array() +--EXPECT-- +BARFOOBAR diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test new file mode 100755 index 0000000..6281671 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test @@ -0,0 +1,20 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "base.twig" %} +{% block content %}{% include "included.twig" %}{% endblock %} + +{% block footer %}Footer{% endblock %} +--TEMPLATE(included.twig)-- +{% extends "base.twig" %} +{% block content %}Included Content{% endblock %} +--TEMPLATE(base.twig)-- +{% block content %}Default Content{% endblock %} + +{% block footer %}Default Footer{% endblock %} +--DATA-- +return array() +--EXPECT-- +Included Content +Default Footer +Footer diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test new file mode 100755 index 0000000..71e7c20 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test @@ -0,0 +1,28 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "foo.twig" %} + +{% block content %} + {% block inside %} + INSIDE OVERRIDDEN + {% endblock %} + + BEFORE + {{ parent() }} + AFTER +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %} + BAR +{% endblock %} +--DATA-- +return array() +--EXPECT-- + +INSIDE OVERRIDDEN + + BEFORE + BAR + + AFTER diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test new file mode 100755 index 0000000..d1876a5 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag accepts Twig_Template instance +--TEMPLATE-- +{% extends foo %} + +{% block content %} +{{ parent() }}FOO +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}BAR{% endblock %} +--DATA-- +return array('foo' => $twig->loadTemplate('foo.twig')) +--EXPECT-- +BARFOO diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/basic.test new file mode 100755 index 0000000..ef59a57 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/basic.test @@ -0,0 +1,15 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{{ _self.input('username') }} +{{ _self.input('password', null, 'password', 1) }} + +{% macro input(name, value, type, size) %} + +{% endmacro %} +--DATA-- +return array() +--EXPECT-- + + + diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/external.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/external.test new file mode 100755 index 0000000..5cd3dae --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/external.test @@ -0,0 +1,17 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import 'forms.twig' as forms %} + +{{ forms.input('username') }} +{{ forms.input('password', null, 'password', 1) }} +--TEMPLATE(forms.twig)-- +{% macro input(name, value, type, size) %} + +{% endmacro %} +--DATA-- +return array() +--EXPECT-- + + + diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/from.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/from.test new file mode 100755 index 0000000..205f591 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/from.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% from 'forms.twig' import foo %} +{% from 'forms.twig' import foo as foobar, bar %} + +{{ foo('foo') }} +{{ foobar('foo') }} +{{ bar('foo') }} +--TEMPLATE(forms.twig)-- +{% macro foo(name) %}foo{{ name }}{% endmacro %} +{% macro bar(name) %}bar{{ name }}{% endmacro %} +--DATA-- +return array() +--EXPECT-- +foofoo +foofoo +barfoo diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/self_import.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/self_import.test new file mode 100755 index 0000000..17756cb --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/macro/self_import.test @@ -0,0 +1,17 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import _self as forms %} + +{{ forms.input('username') }} +{{ forms.input('password', null, 'password', 1) }} + +{% macro input(name, value, type, size) %} + +{% endmacro %} +--DATA-- +return array() +--EXPECT-- + + + diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/raw.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/raw.test new file mode 100755 index 0000000..86e55fd --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/raw.test @@ -0,0 +1,10 @@ +--TEST-- +"autoescape" tag does not escape when raw is used as a filter +--TEMPLATE-- +{% autoescape true %} +{{ var|raw }} +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +
    diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/basic.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/basic.test new file mode 100755 index 0000000..75ad6d3 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/basic.test @@ -0,0 +1,17 @@ +--TEST-- +"set" tag +--TEMPLATE-- +{% set foo = 'foo' %} + +{{ foo }} + +{% set foo, bar = 'foo', 'bar' %} + +{{ foo }}{{ bar }} +--DATA-- +return array() +--EXPECT-- +foo + + +foobar diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/capture.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/capture.test new file mode 100755 index 0000000..4408ca8 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/capture.test @@ -0,0 +1,10 @@ +--TEST-- +"set" tag block capture +--TEMPLATE-- +{% set foo %}foo{% endset %} + +{{ foo }} +--DATA-- +return array() +--EXPECT-- +foo diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/expression.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/expression.test new file mode 100755 index 0000000..8ff434a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/set/expression.test @@ -0,0 +1,12 @@ +--TEST-- +"set" tag +--TEMPLATE-- +{% set foo, bar = 'foo' ~ 'bar', 'bar' ~ 'foo' %} + +{{ foo }} +{{ bar }} +--DATA-- +return array() +--EXPECT-- +foobar +barfoo diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/spaceless/simple.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/spaceless/simple.test new file mode 100755 index 0000000..dd06dec --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tags/spaceless/simple.test @@ -0,0 +1,12 @@ +--TEST-- +"spaceless" tag removes whites between HTML tags +--TEMPLATE-- +{% spaceless %} + +
    foo
    + +{% endspaceless %} +--DATA-- +return array() +--EXPECT-- +
    foo
    diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/constant.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/constant.test new file mode 100755 index 0000000..fb3d288 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/constant.test @@ -0,0 +1,12 @@ +--TEST-- +"const" test +--TEMPLATE-- +{{ 8 is constant('E_NOTICE') ? 'ok' : 'no' }} +{{ 'bar' is constant('Foo::BAR_NAME') ? 'ok' : 'no' }} +{{ value is constant('Foo::BAR_NAME') ? 'ok' : 'no' }} +--DATA-- +return array('value' => 'bar'); +--EXPECT-- +ok +ok +ok \ No newline at end of file diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/defined.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/defined.test new file mode 100755 index 0000000..a401b5e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/defined.test @@ -0,0 +1,16 @@ +--TEST-- +"defined" test +--TEMPLATE-- +{{ foo is defined ? 'ok' : 'ko' }} +{{ bar is defined ? 'ok' : 'ko' }} +{{ foobar is not defined ? 'ok' : 'ko' }} +{{ nested.foo is defined ? 'ok' : 'ko' }} +{{ nested.bar is not defined ? 'ok' : 'ko' }} +--DATA-- +return array('foo' => 'bar', 'bar' => null, 'nested' => array('foo' => 'foo')); +--EXPECT-- +ok +ok +ok +ok +ok diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/empty.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/empty.test new file mode 100755 index 0000000..d183a93 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/empty.test @@ -0,0 +1,12 @@ +--TEST-- +"empty" test +--TEMPLATE-- +{{ foo is empty ? 'ok' : 'ko' }} +{{ bar is empty ? 'ok' : 'ko' }} +{{ foobar is empty ? 'ok' : 'ko' }} +--DATA-- +return array('foo' => '', 'bar' => null, 'foobar' => false); +--EXPECT-- +ok +ok +ok diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/even.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/even.test new file mode 100755 index 0000000..695b4c2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/even.test @@ -0,0 +1,14 @@ +--TEST-- +"even" test +--TEMPLATE-- +{{ 1 is even ? 'ko' : 'ok' }} +{{ 2 is even ? 'ok' : 'ko' }} +{{ 1 is not even ? 'ok' : 'ko' }} +{{ 2 is not even ? 'ko' : 'ok' }} +--DATA-- +return array() +--EXPECT-- +ok +ok +ok +ok diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/odd.test b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/odd.test new file mode 100755 index 0000000..1b8311e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Fixtures/tests/odd.test @@ -0,0 +1,10 @@ +--TEST-- +"odd" test +--TEMPLATE-- +{{ 1 is odd ? 'ok' : 'ko' }} +{{ 2 is odd ? 'ko' : 'ok' }} +--DATA-- +return array() +--EXPECT-- +ok +ok \ No newline at end of file diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/LexerTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/LexerTest.php new file mode 100755 index 0000000..f589712 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/LexerTest.php @@ -0,0 +1,39 @@ +assertEquals(2, $this->countToken($template, Twig_Token::PUNCTUATION_TYPE, '{')); + $this->assertEquals(2, $this->countToken($template, Twig_Token::PUNCTUATION_TYPE, '}')); + } + + protected function countToken($template, $type, $value = null) + { + $lexer = new Twig_Lexer(new Twig_Environment()); + $stream = $lexer->tokenize($template); + + $count = 0; + $tokens = array(); + while (!$stream->isEOF()) { + $token = $stream->next(); + if ($type === $token->getType()) { + if (null === $value || $value === $token->getValue()) { + ++$count; + } + } + } + + return $count; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Loader/FilesystemTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Loader/FilesystemTest.php new file mode 100755 index 0000000..30d0c4d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Loader/FilesystemTest.php @@ -0,0 +1,46 @@ +getCacheKey($template); + } + + public function getSecurityTests() + { + return array( + array('..\\AutoloaderTest.php'), + array('..\\\\\\AutoloaderTest.php'), + array('../AutoloaderTest.php'), + array('..////AutoloaderTest.php'), + array('./../AutoloaderTest.php'), + array('.\\..\\AutoloaderTest.php'), + array('././././././../AutoloaderTest.php'), + array('.\\./.\\./.\\./../AutoloaderTest.php'), + array('foo/../../AutoloaderTest.php'), + array('foo\\..\\..\\AutoloaderTest.php'), + array('foo/../bar/../../AutoloaderTest.php'), + array('foo/bar/../../../AutoloaderTest.php'), + array('filters/../../AutoloaderTest.php'), + array('filters//..//..//AutoloaderTest.php'), + array('filters\\..\\..\\AutoloaderTest.php'), + array('filters\\\\..\\\\..\\\\AutoloaderTest.php'), + array('filters\\//../\\/\\..\\AutoloaderTest.php'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/AutoEscapeTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/AutoEscapeTest.php new file mode 100755 index 0000000..ebfcb48 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/AutoEscapeTest.php @@ -0,0 +1,46 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals(true, $node->getAttribute('value')); + } + + /** + * @covers Twig_Node_AutoEscape::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $body = new Twig_Node(array(new Twig_Node_Text('foo', 0))); + $node = new Twig_Node_AutoEscape(true, $body, 0); + + return array( + array($node, 'echo "foo";'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockReferenceTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockReferenceTest.php new file mode 100755 index 0000000..f1c5ab1 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockReferenceTest.php @@ -0,0 +1,41 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + /** + * @covers Twig_Node_BlockReference::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + return array( + array(new Twig_Node_BlockReference('foo', 0), '$this->displayBlock(\'foo\', $context, $blocks);'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockTest.php new file mode 100755 index 0000000..6bc5e79 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/BlockTest.php @@ -0,0 +1,52 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals('foo', $node->getAttribute('name')); + } + + /** + * @covers Twig_Node_Block::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $body = new Twig_Node_Text('foo', 0); + $node = new Twig_Node_Block('foo', $body, 0); + + return array( + array($node, << $foo = new Twig_Node_Expression_Constant('bar', 0)); + $node = new Twig_Node_Expression_Array($elements, 0); + + $this->assertEquals($foo, $node->getNode('foo')); + } + + /** + * @covers Twig_Node_Expression_Array::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $elements = array( + 'foo' => new Twig_Node_Expression_Constant('bar', 0), + 'bar' => new Twig_Node_Expression_Constant('foo', 0), + ); + $node = new Twig_Node_Expression_Array($elements, 0); + + return array( + array($node, 'array("foo" => "bar", "bar" => "foo")'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/AssignNameTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/AssignNameTest.php new file mode 100755 index 0000000..28bc667 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/AssignNameTest.php @@ -0,0 +1,43 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + /** + * @covers Twig_Node_Expression_AssignName::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $node = new Twig_Node_Expression_AssignName('foo', 0); + + return array( + array($node, '$context[\'foo\']'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AddTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AddTest.php new file mode 100755 index 0000000..80d318f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AddTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_Add::compile + * @covers Twig_Node_Expression_Binary_Add::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_Add($left, $right, 0); + + return array( + array($node, '(1 + 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AndTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AndTest.php new file mode 100755 index 0000000..a6ed4f2 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/AndTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_And::compile + * @covers Twig_Node_Expression_Binary_And::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_And($left, $right, 0); + + return array( + array($node, '(1 && 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php new file mode 100755 index 0000000..a04edc9 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_Concat::compile + * @covers Twig_Node_Expression_Binary_Concat::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_Concat($left, $right, 0); + + return array( + array($node, '(1 . 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/DivTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/DivTest.php new file mode 100755 index 0000000..42002fc --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/DivTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_Div::compile + * @covers Twig_Node_Expression_Binary_Div::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_Div($left, $right, 0); + + return array( + array($node, '(1 / 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php new file mode 100755 index 0000000..cfa335c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_FloorDiv::compile + * @covers Twig_Node_Expression_Binary_FloorDiv::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_FloorDiv($left, $right, 0); + + return array( + array($node, 'floor((1 / 2))'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ModTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ModTest.php new file mode 100755 index 0000000..67612a5 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/ModTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_Mod::compile + * @covers Twig_Node_Expression_Binary_Mod::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_Mod($left, $right, 0); + + return array( + array($node, '(1 % 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/MulTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/MulTest.php new file mode 100755 index 0000000..c817d02 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/MulTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_Mul::compile + * @covers Twig_Node_Expression_Binary_Mul::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_Mul($left, $right, 0); + + return array( + array($node, '(1 * 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/OrTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/OrTest.php new file mode 100755 index 0000000..d8e2f8d --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/OrTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_Or::compile + * @covers Twig_Node_Expression_Binary_Or::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_Or($left, $right, 0); + + return array( + array($node, '(1 || 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/SubTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/SubTest.php new file mode 100755 index 0000000..6583d09 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Binary/SubTest.php @@ -0,0 +1,49 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + /** + * @covers Twig_Node_Expression_Binary_Sub::compile + * @covers Twig_Node_Expression_Binary_Sub::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 0); + $right = new Twig_Node_Expression_Constant(2, 0); + $node = new Twig_Node_Expression_Binary_Sub($left, $right, 0); + + return array( + array($node, '(1 - 2)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConditionalTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConditionalTest.php new file mode 100755 index 0000000..f7cce28 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConditionalTest.php @@ -0,0 +1,52 @@ +assertEquals($expr1, $node->getNode('expr1')); + $this->assertEquals($expr2, $node->getNode('expr2')); + $this->assertEquals($expr3, $node->getNode('expr3')); + } + + /** + * @covers Twig_Node_Expression_Conditional::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $expr1 = new Twig_Node_Expression_Constant(1, 0); + $expr2 = new Twig_Node_Expression_Constant(2, 0); + $expr3 = new Twig_Node_Expression_Constant(3, 0); + $node = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, 0); + $tests[] = array($node, '(1) ? (2) : (3)'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConstantTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConstantTest.php new file mode 100755 index 0000000..0cf3867 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ConstantTest.php @@ -0,0 +1,44 @@ +assertEquals('foo', $node->getAttribute('value')); + } + + /** + * @covers Twig_Node_Expression_Constant::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $node = new Twig_Node_Expression_Constant('foo', 0); + $tests[] = array($node, '"foo"'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/FilterTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/FilterTest.php new file mode 100755 index 0000000..e4c31d0 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/FilterTest.php @@ -0,0 +1,75 @@ +assertEquals($expr, $node->getNode('node')); + $this->assertEquals($name, $node->getNode('filter')); + $this->assertEquals($args, $node->getNode('arguments')); + } + + /** + * @covers Twig_Node_Expression_Filter::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + + $expr = new Twig_Node_Expression_Constant('foo', 0); + $node = $this->createFilter($expr, 'foobar', array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))); + + $tests[] = array($node, '$this->resolveMissingFilter("foobar", array("foo", "bar", "foobar"))'); + + try { + $node->compile($this->getCompiler()); + $this->fail(); + } catch (Exception $e) { + $this->assertEquals('Twig_Error_Syntax', get_class($e)); + } + } + + public function getTests() + { + $tests = array(); + + $expr = new Twig_Node_Expression_Constant('foo', 0); + $node = $this->createFilter($expr, 'upper'); + $node = $this->createFilter($node, 'lower', array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))); + + if (function_exists('mb_get_info')) { + $tests[] = array($node, 'twig_lower_filter($this->env, twig_upper_filter($this->env, "foo"), "bar", "foobar")'); + } else { + $tests[] = array($node, 'strtolower(strtoupper("foo"), "bar", "foobar")'); + } + + return $tests; + } + + protected function createFilter($node, $name, array $arguments = array()) + { + $name = new Twig_Node_Expression_Constant($name, 0); + $arguments = new Twig_Node($arguments); + return new Twig_Node_Expression_Filter($node, $name, $arguments, 0); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/GetAttrTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/GetAttrTest.php new file mode 100755 index 0000000..a4fbb96 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/GetAttrTest.php @@ -0,0 +1,67 @@ +assertEquals($expr, $node->getNode('node')); + $this->assertEquals($attr, $node->getNode('attribute')); + $this->assertEquals($args, $node->getNode('arguments')); + $this->assertEquals(Twig_Node_Expression_GetAttr::TYPE_ARRAY, $node->getAttribute('type')); + } + + /** + * @covers Twig_Node_Expression_GetAttr::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $expr = new Twig_Node_Expression_Name('foo', 0); + $attr = new Twig_Node_Expression_Constant('bar', 0); + $args = new Twig_Node(); + $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Node_Expression_GetAttr::TYPE_ANY, 0); + $tests[] = array($node, '$this->getAttribute((isset($context[\'foo\']) ? $context[\'foo\'] : null), "bar", array(), "any", false, 0)'); + + $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Node_Expression_GetAttr::TYPE_ARRAY, 0); + $tests[] = array($node, '$this->getAttribute((isset($context[\'foo\']) ? $context[\'foo\'] : null), "bar", array(), "array", false, 0)'); + + + $args = new Twig_Node(array( + new Twig_Node_Expression_Name('foo', 0), + new Twig_Node_Expression_Constant('bar', 0), + )); + $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Node_Expression_GetAttr::TYPE_METHOD, 0); + $tests[] = array($node, '$this->getAttribute((isset($context[\'foo\']) ? $context[\'foo\'] : null), "bar", array((isset($context[\'foo\']) ? $context[\'foo\'] : null), "bar", ), "method", false, 0)'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/NameTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/NameTest.php new file mode 100755 index 0000000..70b6654 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/NameTest.php @@ -0,0 +1,50 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + /** + * @covers Twig_Node_Expression_Name::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Name('foo', 0); + $self = new Twig_Node_Expression_Name('_self', 0); + $context = new Twig_Node_Expression_Name('_context', 0); + + $env = new Twig_Environment(null, array('strict_variables' => true)); + + return array( + array($node, '$this->getContext($context, \'foo\', \'0\')', $env), + array($node, '(isset($context[\'foo\']) ? $context[\'foo\'] : null)'), + array($self, '$this'), + array($context, '$context'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ParentTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ParentTest.php new file mode 100755 index 0000000..ef18925 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/ParentTest.php @@ -0,0 +1,42 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + /** + * @covers Twig_Node_Parent::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + $tests[] = array(new Twig_Node_Expression_Parent('foo', 0), '$this->renderParentBlock("foo", $context, $blocks)'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NegTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NegTest.php new file mode 100755 index 0000000..218de6e --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NegTest.php @@ -0,0 +1,46 @@ +assertEquals($expr, $node->getNode('node')); + } + + /** + * @covers Twig_Node_Expression_Unary_Neg::compile + * @covers Twig_Node_Expression_Unary_Neg::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Constant(1, 0); + $node = new Twig_Node_Expression_Unary_Neg($node, 0); + + return array( + array($node, '(-1)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NotTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NotTest.php new file mode 100755 index 0000000..ed6349c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/NotTest.php @@ -0,0 +1,46 @@ +assertEquals($expr, $node->getNode('node')); + } + + /** + * @covers Twig_Node_Expression_Unary_Not::compile + * @covers Twig_Node_Expression_Unary_Not::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Constant(1, 0); + $node = new Twig_Node_Expression_Unary_Not($node, 0); + + return array( + array($node, '(!1)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/PosTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/PosTest.php new file mode 100755 index 0000000..6a414bc --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/Expression/Unary/PosTest.php @@ -0,0 +1,46 @@ +assertEquals($expr, $node->getNode('node')); + } + + /** + * @covers Twig_Node_Expression_Unary_Pos::compile + * @covers Twig_Node_Expression_Unary_Pos::operator + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Constant(1, 0); + $node = new Twig_Node_Expression_Unary_Pos($node, 0); + + return array( + array($node, '(+1)'), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ForTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ForTest.php new file mode 100755 index 0000000..b1a1457 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ForTest.php @@ -0,0 +1,163 @@ +setAttribute('with_loop', false); + + $this->assertEquals($keyTarget, $node->getNode('key_target')); + $this->assertEquals($valueTarget, $node->getNode('value_target')); + $this->assertEquals($seq, $node->getNode('seq')); + $this->assertEquals($body, $node->getNode('body')); + $this->assertEquals(null, $node->getNode('else')); + + $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0); + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, 0); + $node->setAttribute('with_loop', false); + $this->assertEquals($else, $node->getNode('else')); + } + + /** + * @covers Twig_Node_For::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $keyTarget = new Twig_Node_Expression_AssignName('key', 0); + $valueTarget = new Twig_Node_Expression_AssignName('item', 0); + $seq = new Twig_Node_Expression_Name('items', 0); + $body = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0); + $else = null; + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, 0); + $node->setAttribute('with_loop', false); + + $tests[] = array($node, << \$context['item']) { + echo (isset(\$context['foo']) ? \$context['foo'] : null); +} +\$_parent = \$context['_parent']; +unset(\$context['_seq'], \$context['_iterated'], \$context['key'], \$context['item'], \$context['_parent'], \$context['loop']); +\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent)); +EOF + ); + + $keyTarget = new Twig_Node_Expression_AssignName('k', 0); + $valueTarget = new Twig_Node_Expression_AssignName('v', 0); + $seq = new Twig_Node_Expression_Name('values', 0); + $body = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0); + $else = null; + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, 0); + $node->setAttribute('with_loop', true); + + $tests[] = array($node, << \$context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, +); +if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) { + \$length = count(\$context['_seq']); + \$context['loop']['revindex0'] = \$length - 1; + \$context['loop']['revindex'] = \$length; + \$context['loop']['length'] = \$length; + \$context['loop']['last'] = 1 === \$length; +} +foreach (\$context['_seq'] as \$context['k'] => \$context['v']) { + echo (isset(\$context['foo']) ? \$context['foo'] : null); + ++\$context['loop']['index0']; + ++\$context['loop']['index']; + \$context['loop']['first'] = false; + if (isset(\$context['loop']['length'])) { + --\$context['loop']['revindex0']; + --\$context['loop']['revindex']; + \$context['loop']['last'] = 0 === \$context['loop']['revindex0']; + } +} +\$_parent = \$context['_parent']; +unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']); +\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent)); +EOF + ); + + $keyTarget = new Twig_Node_Expression_AssignName('k', 0); + $valueTarget = new Twig_Node_Expression_AssignName('v', 0); + $seq = new Twig_Node_Expression_Name('values', 0); + $body = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0); + $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0); + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, 0); + $node->setAttribute('with_loop', true); + + $tests[] = array($node, << \$context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, +); +if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) { + \$length = count(\$context['_seq']); + \$context['loop']['revindex0'] = \$length - 1; + \$context['loop']['revindex'] = \$length; + \$context['loop']['length'] = \$length; + \$context['loop']['last'] = 1 === \$length; +} +foreach (\$context['_seq'] as \$context['k'] => \$context['v']) { + echo (isset(\$context['foo']) ? \$context['foo'] : null); + \$context['_iterated'] = true; + ++\$context['loop']['index0']; + ++\$context['loop']['index']; + \$context['loop']['first'] = false; + if (isset(\$context['loop']['length'])) { + --\$context['loop']['revindex0']; + --\$context['loop']['revindex']; + \$context['loop']['last'] = 0 === \$context['loop']['revindex0']; + } +} +if (!\$context['_iterated']) { + echo (isset(\$context['foo']) ? \$context['foo'] : null); +} +\$_parent = \$context['_parent']; +unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']); +\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent)); +EOF + ); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IfTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IfTest.php new file mode 100755 index 0000000..ff252b5 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IfTest.php @@ -0,0 +1,99 @@ +assertEquals($t, $node->getNode('tests')); + $this->assertEquals(null, $node->getNode('else')); + + $else = new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 0), 0); + $node = new Twig_Node_If($t, $else, 0); + $this->assertEquals($else, $node->getNode('else')); + } + + /** + * @covers Twig_Node_If::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $t = new Twig_Node(array( + new Twig_Node_Expression_Constant(true, 0), + new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0), + ), array(), 0); + $else = null; + $node = new Twig_Node_If($t, $else, 0); + + $tests[] = array($node, <<assertEquals($macro, $node->getNode('expr')); + $this->assertEquals($var, $node->getNode('var')); + } + + /** + * @covers Twig_Node_Import::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $macro = new Twig_Node_Expression_Constant('foo.twig', 0); + $var = new Twig_Node_Expression_AssignName('macro', 0); + $node = new Twig_Node_Import($macro, $var, 0); + + $tests[] = array($node, '$context[\'macro\'] = $this->env->loadTemplate("foo.twig", true);'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IncludeTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IncludeTest.php new file mode 100755 index 0000000..aa876ee --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/IncludeTest.php @@ -0,0 +1,77 @@ +assertEquals(null, $node->getNode('variables')); + $this->assertEquals($expr, $node->getNode('expr')); + $this->assertFalse($node->getAttribute('only')); + + $vars = new Twig_Node_Expression_Array(array('foo' => new Twig_Node_Expression_Constant(true, 0)), 0); + $node = new Twig_Node_Include($expr, $vars, true, 0); + $this->assertEquals($vars, $node->getNode('variables')); + $this->assertTrue($node->getAttribute('only')); + } + + /** + * @covers Twig_Node_Include::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $expr = new Twig_Node_Expression_Constant('foo.twig', 0); + $node = new Twig_Node_Include($expr, null, false, 0); + $tests[] = array($node, '$this->env->loadTemplate("foo.twig")->display($context);'); + + $expr = new Twig_Node_Expression_Conditional( + new Twig_Node_Expression_Constant(true, 0), + new Twig_Node_Expression_Constant('foo', 0), + new Twig_Node_Expression_Constant('foo', 0), + 0 + ); + $node = new Twig_Node_Include($expr, null, false, 0); + $tests[] = array($node, <<env->loadTemplate(\$template); +} +\$template->display(\$context); +EOF + ); + + $expr = new Twig_Node_Expression_Constant('foo.twig', 0); + $vars = new Twig_Node_Expression_Array(array('foo' => new Twig_Node_Expression_Constant(true, 0)), 0); + $node = new Twig_Node_Include($expr, $vars, false, 0); + $tests[] = array($node, '$this->env->loadTemplate("foo.twig")->display(array_merge($context, array("foo" => true)));'); + + $node = new Twig_Node_Include($expr, $vars, true, 0); + $tests[] = array($node, '$this->env->loadTemplate("foo.twig")->display(array("foo" => true));'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/MacroTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/MacroTest.php new file mode 100755 index 0000000..904cfb4 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/MacroTest.php @@ -0,0 +1,62 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals($arguments, $node->getNode('arguments')); + $this->assertEquals('foo', $node->getAttribute('name')); + } + + /** + * @covers Twig_Node_Macro::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $body = new Twig_Node_Text('foo', 0); + $arguments = new Twig_Node(array(new Twig_Node_Expression_Name('foo', 0)), array(), 0); + $node = new Twig_Node_Macro('foo', $body, $arguments, 0); + + return array( + array($node, <<env->getGlobals(), array( + "foo" => \$foo, + )); + + ob_start(); + echo "foo"; + + return ob_get_clean(); +} +EOF + ), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ModuleTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ModuleTest.php new file mode 100755 index 0000000..b0f94de --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/ModuleTest.php @@ -0,0 +1,170 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals($blocks, $node->getNode('blocks')); + $this->assertEquals($macros, $node->getNode('macros')); + $this->assertEquals($parent, $node->getNode('parent')); + $this->assertEquals($filename, $node->getAttribute('filename')); + } + + /** + * @covers Twig_Node_Module::compile + * @covers Twig_Node_Module::compileTemplate + * @covers Twig_Node_Module::compileMacros + * @covers Twig_Node_Module::compileClassHeader + * @covers Twig_Node_Module::compileDisplayHeader + * @covers Twig_Node_Module::compileDisplayBody + * @covers Twig_Node_Module::compileDisplayFooter + * @covers Twig_Node_Module::compileClassFooter + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $twig = new Twig_Environment(new Twig_Loader_String()); + + $tests = array(); + + $body = new Twig_Node_Text('foo', 0); + $extends = null; + $blocks = new Twig_Node(); + $macros = new Twig_Node(); + $filename = 'foo.twig'; + + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename); + $tests[] = array($node, <<env->getGlobals(), \$context); + + echo "foo"; + } + + public function getTemplateName() + { + return "foo.twig"; + } +} +EOF + , $twig); + + $import = new Twig_Node_Import(new Twig_Node_Expression_Constant('foo.twig', 0), new Twig_Node_Expression_AssignName('macro', 0), 0); + + $body = new Twig_Node(array($import, new Twig_Node_Text('foo', 0))); + $extends = new Twig_Node_Expression_Constant('layout.twig', 0); + + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename); + $tests[] = array($node, <<parent) { + \$this->parent = \$this->env->loadTemplate("layout.twig"); + } + + return \$this->parent; + } + + public function display(array \$context, array \$blocks = array()) + { + \$context = array_merge(\$this->env->getGlobals(), \$context); + + \$context['macro'] = \$this->env->loadTemplate("foo.twig", true); + \$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks)); + } + + public function getTemplateName() + { + return "foo.twig"; + } +} +EOF + , $twig); + + $body = new Twig_Node_Text('foo', 0); + $extends = new Twig_Node_Expression_Conditional( + new Twig_Node_Expression_Constant(true, 0), + new Twig_Node_Expression_Constant('foo', 0), + new Twig_Node_Expression_Constant('foo', 0), + 0 + ); + + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename); + $tests[] = array($node, <<parent) { + \$this->parent = (true) ? ("foo") : ("foo"); + if (!\$this->parent instanceof Twig_Template) { + \$this->parent = \$this->env->loadTemplate(\$this->parent); + } + } + + return \$this->parent; + } + + public function display(array \$context, array \$blocks = array()) + { + \$context = array_merge(\$this->env->getGlobals(), \$context); + + \$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks)); + } + + public function getTemplateName() + { + return "foo.twig"; + } +} +EOF + , $twig); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/PrintTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/PrintTest.php new file mode 100755 index 0000000..168663c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/PrintTest.php @@ -0,0 +1,43 @@ +assertEquals($expr, $node->getNode('expr')); + } + + /** + * @covers Twig_Node_Print::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + $tests[] = array(new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 0), 0), 'echo "foo";'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxTest.php new file mode 100755 index 0000000..1610873 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxTest.php @@ -0,0 +1,57 @@ +assertEquals($body, $node->getNode('body')); + } + + /** + * @covers Twig_Node_Sandbox::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $body = new Twig_Node_Text('foo', 0); + $node = new Twig_Node_Sandbox($body, 0); + + $tests[] = array($node, <<env->getExtension('sandbox'); +if (!\$alreadySandboxed = \$sandbox->isSandboxed()) { + \$sandbox->enableSandbox(); +} +echo "foo"; +if (!\$alreadySandboxed) { + \$sandbox->disableSandbox(); +} +EOF + ); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedModuleTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedModuleTest.php new file mode 100755 index 0000000..209b2de --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedModuleTest.php @@ -0,0 +1,145 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals($blocks, $node->getNode('blocks')); + $this->assertEquals($macros, $node->getNode('macros')); + $this->assertEquals($parent, $node->getNode('parent')); + $this->assertEquals($filename, $node->getAttribute('filename')); + } + + /** + * @covers Twig_Node_SandboxedModule::compile + * @covers Twig_Node_SandboxedModule::compileDisplayBody + * @covers Twig_Node_SandboxedModule::compileDisplayFooter + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $twig = new Twig_Environment(new Twig_Loader_String()); + + $tests = array(); + + $body = new Twig_Node_Text('foo', 0); + $extends = null; + $blocks = new Twig_Node(); + $macros = new Twig_Node(); + $filename = 'foo.twig'; + + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename); + $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle')); + + $tests[] = array($node, <<checkSecurity(); + \$context = array_merge(\$this->env->getGlobals(), \$context); + + echo "foo"; + } + + protected function checkSecurity() { + \$this->env->getExtension('sandbox')->checkSecurity( + array('upper'), + array('for'), + array('cycle') + ); + } + + public function getTemplateName() + { + return "foo.twig"; + } +} +EOF + , $twig); + + $body = new Twig_Node_Text('foo', 0); + $extends = new Twig_Node_Expression_Constant('layout.twig', 0); + $blocks = new Twig_Node(); + $macros = new Twig_Node(); + $filename = 'foo.twig'; + + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename); + $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle')); + + $tests[] = array($node, <<parent) { + \$this->parent = \$this->env->loadTemplate("layout.twig"); + } + + return \$this->parent; + } + + public function display(array \$context, array \$blocks = array()) + { + \$context = array_merge(\$this->env->getGlobals(), \$context); + + \$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks)); + } + + protected function checkSecurity() { + \$this->env->getExtension('sandbox')->checkSecurity( + array('upper'), + array('for'), + array('cycle') + ); + + \$this->parent->checkSecurity(); + } + + public function getTemplateName() + { + return "foo.twig"; + } +} +EOF + , $twig); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedPrintTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedPrintTest.php new file mode 100755 index 0000000..6e30241 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SandboxedPrintTest.php @@ -0,0 +1,49 @@ +assertEquals($expr, $node->getNode('expr')); + } + + /** + * @covers Twig_Node_SandboxedPrint::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $tests[] = array(new Twig_Node_SandboxedPrint(new Twig_Node_Expression_Constant('foo', 0), 0), <<env->getExtension('sandbox')->checkMethodAllowed(\$_tmp, '__toString'); +} +echo "foo"; +EOF + ); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SetTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SetTest.php new file mode 100755 index 0000000..7883fba --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/SetTest.php @@ -0,0 +1,68 @@ +assertEquals($names, $node->getNode('names')); + $this->assertEquals($values, $node->getNode('values')); + $this->assertEquals(false, $node->getAttribute('capture')); + } + + /** + * @covers Twig_Node_Set::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + + $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 0)), array(), 0); + $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 0)), array(), 0); + $node = new Twig_Node_Set(false, $names, $values, 0); + $tests[] = array($node, '$context[\'foo\'] = "foo";'); + + $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 0)), array(), 0); + $values = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 0), 0)), array(), 0); + $node = new Twig_Node_Set(true, $names, $values, 0); + $tests[] = array($node, <<
    foo
    ', 0))); + $node = new Twig_Node_Spaceless($body, 0); + + $this->assertEquals($body, $node->getNode('body')); + } + + /** + * @covers Twig_Node_Spaceless::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $body = new Twig_Node(array(new Twig_Node_Text('
    foo
    ', 0))); + $node = new Twig_Node_Spaceless($body, 0); + + return array( + array($node, <<
    foo
    "; +echo trim(preg_replace('/>\s+<', ob_get_clean())); +EOF + ), + ); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TestCase.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TestCase.php new file mode 100755 index 0000000..c5b974f --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TestCase.php @@ -0,0 +1,40 @@ +assertNodeCompilation($source, $node, $environment); + } + + public function assertNodeCompilation($source, Twig_Node $node, Twig_Environment $environment = null) + { + $compiler = $this->getCompiler($environment); + $compiler->compile($node); + + $this->assertEquals($source, trim($compiler->getSource())); + } + + protected function getCompiler(Twig_Environment $environment = null) + { + return new Twig_Compiler(null === $environment ? $this->getEnvironment() : $environment); + } + + protected function getEnvironment() + { + return new Twig_Environment(); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TextTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TextTest.php new file mode 100755 index 0000000..87e0337 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/Node/TextTest.php @@ -0,0 +1,42 @@ +assertEquals('foo', $node->getAttribute('data')); + } + + /** + * @covers Twig_Node_Text::compile + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null) + { + parent::testCompile($node, $source, $environment); + } + + public function getTests() + { + $tests = array(); + $tests[] = array(new Twig_Node_Text('foo', 0), 'echo "foo";'); + + return $tests; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/NodeVisitor/OptimizerTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/NodeVisitor/OptimizerTest.php new file mode 100755 index 0000000..96ccfc7 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/NodeVisitor/OptimizerTest.php @@ -0,0 +1,72 @@ + false)); + $env->addExtension(new Twig_Extension_Optimizer()); + + $stream = $env->parse($env->tokenize($template, 'index')); + + foreach ($expected as $target => $withLoop) { + $this->assertTrue($this->checkForConfiguration($stream, $target, $withLoop), sprintf('variable %s is %soptimized', $target, $withLoop ? 'not ' : '')); + } + } + + public function getTestsForForOptimizer() + { + return array( + array('{% for i in foo %}{% endfor %}', array('i' => false)), + + array('{% for i in foo %}{{ loop.index }}{% endfor %}', array('i' => true)), + + array('{% for i in foo %}{% for j in foo %}{% endfor %}{% endfor %}', array('i' => false, 'j' => false)), + + array('{% for i in foo %}{% include "foo" %}{% endfor %}', array('i' => true)), + + array('{% for i in foo %}{% include "foo" only %}{% endfor %}', array('i' => false)), + + array('{% for i in foo %}{% for j in foo %}{{ loop.index }}{% endfor %}{% endfor %}', array('i' => false, 'j' => true)), + + array('{% for i in foo %}{% for j in foo %}{{ loop.parent.loop.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => true)), + + array('{% for i in foo %}{% set l = loop %}{% for j in foo %}{{ l.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => false)), + + array('{% for i in foo %}{% for j in foo %}{{ foo.parent.loop.index }}{% endfor %}{% endfor %}', array('i' => false, 'j' => false)), + + array('{% for i in foo %}{% for j in foo %}{{ loop["parent"].loop.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => true)), + ); + } + + public function checkForConfiguration(Twig_NodeInterface $node = null, $target, $withLoop) + { + if (null === $node) { + return; + } + + foreach ($node as $n) { + if ($n instanceof Twig_Node_For) { + if ($target === $n->getNode('value_target')->getAttribute('name')) { + return $withLoop == $n->getAttribute('with_loop'); + } + } + + $ret = $this->checkForConfiguration($n, $target, $withLoop); + if (null !== $ret) { + return $ret; + } + } + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/ParserTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/ParserTest.php new file mode 100755 index 0000000..2159a5c --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/ParserTest.php @@ -0,0 +1,21 @@ +setMacro('display', $this->getMock('Twig_Node_Macro', null, array(), '', null)); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/TemplateTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/TemplateTest.php new file mode 100755 index 0000000..77d5112 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/TemplateTest.php @@ -0,0 +1,178 @@ + 'foo', 'bar' => 'value')), + array(new Twig_TemplateObjectArrayAccess()), + ); + + return $tests; + } + + /** + * @dataProvider getUnkownPropertyOnArrayTests + * @expectedException Twig_Error_Runtime + */ + public function testUnkownPropertyOnArray($array) + { + $env = new Twig_Environment(null, array('strict_variables' => true)); + $template = new Twig_TemplateTest($env); + + $template->getAttribute($array, 'unknown', array(), Twig_Node_Expression_GetAttr::TYPE_ARRAY); + } + + /** + * @dataProvider getGetAttributeTests + */ + public function testGetAttribute($expected, $object, $item, $arguments, $type) + { + $template = new Twig_TemplateTest(new Twig_Environment()); + + $this->assertEquals($expected, $template->getAttribute($object, $item, $arguments, $type)); + } + + public function getGetAttributeTests() + { + $array = array('foo' => 'foo'); + $object = new Twig_TemplateObject(); + $objectArray = new Twig_TemplateObjectArrayAccess(); + $objectMagic = new Twig_TemplateObjectMagic(); + + $anyType = Twig_Node_Expression_GetAttr::TYPE_ANY; + $methodType = Twig_Node_Expression_GetAttr::TYPE_METHOD; + $arrayType = Twig_Node_Expression_GetAttr::TYPE_ARRAY; + + $tests = array( + // ARRAY + array('foo', $array, 'foo', array(), $arrayType), + array(null, $array, 'foobar', array(), $arrayType), + array('foo', $objectArray, 'foo', array(), $arrayType), + array(null, $objectArray, 'foobar', array(), $arrayType), + + // METHOD + array('bar', $object, 'bar', array(), $methodType), + array('bar', $object, 'getBar', array(), $methodType), + array('bar', $object, 'getbar', array(), $methodType), + array('foobar', $object, 'foobar', array(), $methodType), + array('babar', $object, 'babar', array(), $methodType), + array('babarStatic', $object, 'babarStatic', array(), $methodType), + array('__call_baz', $objectMagic, 'baz', array(), $methodType), + array('__call_Baz', $objectMagic, 'Baz', array(), $methodType), + + // ANY + array('foo', $object, 'foo', array(), $anyType), + array('foo', $objectMagic, 'foo', array(), $anyType), + array('Foo', $objectMagic, 'Foo', array(), $anyType), + array(null, $object, 'null', array(), $anyType), + ); + + // add the same tests for the any type + foreach ($tests as $test) { + if ($anyType !== $test[4]) { + $test[4] = $anyType; + $tests[] = $test; + } + } + + return $tests; + } +} + +class Twig_TemplateTest extends Twig_Template +{ + public function display(array $context) + { + } + + public function getAttribute($object, $item, array $arguments = array(), $type = Twig_Node_Expression_GetAttr::TYPE_ANY, $noStrictCheck = false, $lineno = -1) + { + return parent::getAttribute($object, $item, $arguments, $type); + } +} + +class Twig_TemplateObject +{ + public $foo = 'foo'; + public $null = null; + protected $babar = 'babar...'; + static protected $babarStatic = 'babarStatic...'; + + static public function getBabarStatic() + { + return 'babarStatic'; + } + + public function getBabar() + { + return 'babar'; + } + + public function getNull() + { + return 'null...'; + } + + public function getBar() + { + return 'bar'; + } + + public function fooBar() + { + return 'foobar'; + } +} + +class Twig_TemplateObjectArrayAccess implements ArrayAccess +{ + public $attributes = array('foo' => 'foo'); + + public function offsetExists($name) + { + return isset($this->attributes[$name]); + } + + public function offsetGet($name) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + public function offsetSet($name, $value) + { + } + + public function offsetUnset($name) + { + } +} + +class Twig_TemplateObjectMagic +{ + public $attributes = array('foo' => 'foo', 'Foo' => 'Foo'); + + public function __isset($name) + { + return isset($this->attributes[$name]); + } + + public function __get($name) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + public function __call($method, $arguments) + { + return '__call_'.$method; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/TokenStreamTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/TokenStreamTest.php new file mode 100755 index 0000000..794a037 --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/TokenStreamTest.php @@ -0,0 +1,41 @@ +isEOF()) { + $token = $stream->next(); + + $repr[] = $token->getValue(); + } + $this->assertEquals('1, 2, 3, 4, 5, 6, 7', implode(', ', $repr), '->next() advances the pointer and returns the current token'); + } +} diff --git a/lib/Twig-1.0.0-RC1/test/Twig/Tests/integrationTest.php b/lib/Twig-1.0.0-RC1/test/Twig/Tests/integrationTest.php new file mode 100755 index 0000000..63d49ea --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/Twig/Tests/integrationTest.php @@ -0,0 +1,178 @@ +getRealpath()); + + if (!preg_match('/--TEST--\s*(.*?)\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { + throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); + } + + $message = $match[1]; + $templates = array(); + preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $match[2], $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2]; + } + + $tests[] = array(str_replace($fixturesDir.'/', '', $file), $test, $message, $templates); + } + + return $tests; + } + + /** + * @dataProvider getTests + */ + public function testIntegration($file, $test, $message, $templates) + { + $loader = new Twig_Loader_Array($templates); + $twig = new Twig_Environment($loader, array('cache' => false)); + $twig->addExtension(new Twig_Extension_Escaper()); + $twig->addExtension(new TestExtension()); + + try { + $template = $twig->loadTemplate('index.twig'); + } catch (Twig_Error_Syntax $e) { + $e->setFilename($file); + + throw $e; + } catch (Exception $e) { + throw new Twig_Error($e->getMessage().' (in '.$file.')'); + } + + preg_match_all('/--DATA--(.*?)--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $output = trim($template->render(eval($match[1].';')), "\n "); + $expected = trim($match[2], "\n "); + + if ($expected != $output) { + echo 'Compiled template that failed:'; + + foreach (array_keys($templates) as $name) { + echo "Template: $name\n"; + $source = $loader->getSource($name); + echo $twig->compile($twig->parse($twig->tokenize($source, $name))); + } + } + $this->assertEquals($expected, $output, $message.' (in '.$file.')'); + } + } +} + +function test_foo($value = 'foo') +{ + return $value; +} + +class Foo +{ + const BAR_NAME = 'bar'; + + public function bar($param1 = null, $param2 = null) + { + return 'bar'.($param1 ? '_'.$param1 : '').($param2 ? '-'.$param2 : ''); + } + + public function getFoo() + { + return 'foo'; + } + + public function getSelf() + { + return $this; + } + + public function is() + { + return 'is'; + } + + public function in() + { + return 'in'; + } + + public function not() + { + return 'not'; + } + + public function strToLower($value) + { + return strtolower($value); + } +} + +class TestExtension extends Twig_Extension +{ + public function getFilters() + { + return array( + 'escape_and_nl2br' => new Twig_Filter_Method($this, 'escape_and_nl2br', array('needs_environment' => true, 'is_safe' => array('html'))), + 'nl2br' => new Twig_Filter_Method($this, 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))), + 'escape_something' => new Twig_Filter_Method($this, 'escape_something', array('is_safe' => array('something'))), + ); + } + + public function getFunctions() + { + return array( + 'safe_br' => new Twig_Function_Method($this, 'br', array('is_safe' => array('html'))), + 'unsafe_br' => new Twig_Function_Method($this, 'br'), + ); + } + + /** + * nl2br which also escapes, for testing escaper filters + */ + public function escape_and_nl2br($env, $value, $sep = '
    ') + { + return $this->nl2br(twig_escape_filter($env, $value, 'html'), $sep); + } + + /** + * nl2br only, for testing filters with pre_escape + */ + public function nl2br($value, $sep = '
    ') + { + // not secure if $value contains html tags (not only entities) + // don't use + return str_replace("\n", "$sep\n", $value); + } + + public function escape_something($value) + { + return strtoupper($value); + } + + public function br() + { + return '
    '; + } + + public function getName() + { + return 'test'; + } +} diff --git a/lib/Twig-1.0.0-RC1/test/bootstrap.php b/lib/Twig-1.0.0-RC1/test/bootstrap.php new file mode 100755 index 0000000..36eb46a --- /dev/null +++ b/lib/Twig-1.0.0-RC1/test/bootstrap.php @@ -0,0 +1,13 @@ +executePreGet($routing); + $this->executeGet($routing); + $this->executePostGet($routing); + break; + case 'POST': + $this->executePrePost($routing); + $this->executePost($routing); + $this->executePostPost($routing); + break; + case 'PUT': + $this->executePrePut($routing); + $this->executePut($routing); + $this->executePostPut($routing); + break; + case 'DELETE': + $this->executePreDelete($routing); + $this->executeDelete($routing); + $this->executePostDelete($routing); + break; + default: + $this->executePreGet($routing); + $this->executeGet($routing); + $this->executePostGet($routing); + break; + } + } + + /** + * Function - Execute Pre Get + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePreGet(\nano\core\routing\Routing $routing){ + + } + + /** + * Function - Execute Get + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executeGet(\nano\core\routing\Routing $routing){ + echo 'Received GET Request'; + } + + /** + * Function - Execute Post Get + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePostGet(\nano\core\routing\Routing $routing){ + + } + + /** + * Function - Execute Pre Post + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePrePost(\nano\core\routing\Routing $routing){ + + } + + /** + * Function - Execute Post + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePost(\nano\core\routing\Routing $routing){ + echo 'Received POST Request'; + } + + /** + * Function - Execute Post Post + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePostPost(\nano\core\routing\Routing $routing){ + + } + + /** + * Function - Execute Pre Put + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePrePut(\nano\core\routing\Routing $routing){ + + } + + /** + * Function - Execute Put + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePut(\nano\core\routing\Routing $routing){ + echo 'Received PUT Request'; + } + + /** + * Function - Execute Post Put + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePostPut(\nano\core\routing\Routing $routing){ + + } + + /** + * Function - Execute Pre Delete + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePreDelete(\nano\core\routing\Routing $routing){ + + } + + /** + * Function - Execute Delete + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executeDelete(\nano\core\routing\Routing $routing){ + echo 'Received DELETE Request'; + } + + /** + * Function - Execute Post Delete + * @param \nano\core\routing\Routing $routing Routing + */ + protected function executePostDelete(\nano\core\routing\Routing $routing){ + + } + +} \ No newline at end of file diff --git a/nano/core/autoloader/Autoloader.class.php b/nano/core/autoloader/Autoloader.class.php new file mode 100755 index 0000000..bc1eb44 --- /dev/null +++ b/nano/core/autoloader/Autoloader.class.php @@ -0,0 +1,106 @@ +$functionName(); + break; + } + } + //push this to the bottom of the stack - can't rely on prepending on spl function + spl_autoload_register(__CLASS__.'::load'); + } + + + /** + * Function - Configure + */ + public static function configure() { + $customConfigClassName = 'project\apps\\'.$_SERVER['PROJECT_APP'].'\Config'; + $autoloads = $customConfigClassName::$autoloads; + foreach($autoloads as $autoload){ + if(isset($autoload['config_class_name'])){ + $className = $autoload['config_class_name']; + $functionName = $autoload['config_call_function_name']; + switch($autoload['config_call_type']){ + case 'static': + $className::$functionName(); + break; + default: + $class = new $className(); + $class->$functionName(); + break; + } + } + } + } +} \ No newline at end of file diff --git a/nano/core/cache/Memcached.class.php b/nano/core/cache/Memcached.class.php new file mode 100644 index 0000000..649450c --- /dev/null +++ b/nano/core/cache/Memcached.class.php @@ -0,0 +1,160 @@ +instanceName = $instanceName; + $this->memcached = new \Memcached(); + $reflect = new \ReflectionClass($this->memcached); + $this->memcachedConstants = $reflect->getConstants(); + $servers = array(); + $memcacheInstances = \nano\core\config\Config::get('memcache_servers'); + foreach($memcacheInstances[$instanceName] as $memcacheHostName => $memcachePort){ + $server = array(); + $server[] = (string)$memcacheHostName; + $server[] = (int)$memcachePort; + $servers[] = $server; + } + $this->memcached->addServers($servers); + //doing some reading on the memcache website, addservers should be used instead of recurse on add server?! + } + + /** + * Function - __clone + */ + final private function __clone() { } + + + /** + * Function - Get Instance + */ + public static function getInstance($instanceName='default'){ + if(!in_array($instanceName,self::$instance)){ + //create default pool + self::$instance[$instanceName] = new self($instanceName); + } + return self::$instance[$instanceName]; + } + + /** + * Function - Get Key + * @param mixed $keys Keys + */ + private function getKey($keys) { + $str = $this->keyPrefix; + if(is_array($keys)){ + foreach($keys as $key){ + $str .= '-'.$key; + } + return $str; + } + return $str.'-'.$keys; + } + + + /** + * Function - Set Key Prefix + * @param mixed $keyPrefix Key Prefix + */ + public function setKeyPrefix($keyPrefix){ + $this->keyPrefix = $keyPrefix; + } + + /** + * Function - Get Key Prefix + * @return mixed + */ + public function getKeyPrefix(){ + return $this->keyPrefix; + } + + /** + * Function - Set + * @param mixed $keys Keys + * @param mixed $value Value + * @param mixed $expires=0 Expires=0 + * @return mixed + */ + public function set($keys,$value,$expires=0){ + $key = $this->getKey($keys); + \nano\core\log\Log::getInstance()->addCachedInfo('[Cache Pool: '.$this->instanceName.'] Set: '.$key); + return $this->memcached->set($key,$value,$expires); + } + + /** + * Function - Get + * @param mixed $keys Keys + * @return mixed + */ + public function get($keys){ + $key = $this->getKey($keys); + \nano\core\log\Log::getInstance()->addCachedInfo('[Cache Pool: '.$this->instanceName.'] Get: '.$key); + return $this->memcached->get($key); + } + + /** + * Function - Delete + * @param mixed $keys Keys + * @return mixed + */ + public function delete($keys){ + $key = $this->getKey($keys); + \nano\core\log\Log::getInstance()->addCachedInfo('[Cache Pool: '.$this->instanceName.'] Delete: '.$key); + return $this->memcached->delete($key); + } + + /** + * Function - Flush + * @return mixed + */ + public function flush(){ + \nano\core\log\Log::getInstance()->addCachedInfo('[Cache Pool: '.$this->instanceName.'] Flush: Flushed Cache'); + return $this->memcached->flush(); + } + + /** + * Function - Get Result Code + * @return mixed + */ + public function getResultCode(){ + return (int)$this->memcached->getResultCode(); + } + + /** + * Function - Get Value From Memcache Constant + * @param mixed $name Name + * @return mixed + */ + public function getValueFromMemcacheConstant($name){ + return isset($this->memcachedConstants[$name])? $this->memcachedConstants[$name] : null; + } + +} \ No newline at end of file diff --git a/nano/core/cli/Cli.class.php b/nano/core/cli/Cli.class.php new file mode 100644 index 0000000..c97881b --- /dev/null +++ b/nano/core/cli/Cli.class.php @@ -0,0 +1,56 @@ +execute(); + \nano\core\view\View::load('nano/core/cli/views/footer.php'); + } + + /** + * Function - Execute + */ + public function execute() { + if(count($_SERVER['argv'])>1){ + if(preg_match('/([\w\d\_\-]+)\:([\w\d\_\-]+)/',$_SERVER['argv'][1],$matches)){ + $file = 'project/cli/'.$matches[1].'/'.$matches[2].'.class.php'; + if(file_exists($file)){ + $class = 'project\cli\\'.$matches[1].'\\'.$matches[2]; + new $class(); + }else{ + $this->termPrint('Failed to find \''.$file.'\'', self::FORMAT_RED, self::FORMAT_BOLD); + } + }else{ + $this->termPrint('Failed to match pattern... (e.g. cron:ExampleFile)', self::FORMAT_RED, self::FORMAT_BOLD); + } + }else{ + $filesAndFolders = \nano\core\helpers\FileSystem::getFilesAndFoldersInDirectory('project/cli'); + $folders = $filesAndFolders['folders']; + foreach($folders as $folderPath => $folderName){ + $this->termPrintln($folderName.':'); + $nestedFilesAndFolders = \nano\core\helpers\FileSystem::getFilesAndFoldersInDirectory($folderPath); + $nestedFiles = $nestedFilesAndFolders['files']; + foreach($nestedFiles as $filePath => $fileName){ + $this->termPrintln("\t".preg_replace('/\..*?$/','',$fileName)); + } + $this->termPrintln(''); + } + } + } +} \ No newline at end of file diff --git a/nano/core/cli/CronBase.class.php b/nano/core/cli/CronBase.class.php new file mode 100644 index 0000000..d719401 --- /dev/null +++ b/nano/core/cli/CronBase.class.php @@ -0,0 +1,14 @@ +execute(); + } + + private final function output($argArray) { + $str = ''; + $escapeSeq = ''; + foreach($argArray as $num => $arg){ + if($num==0){ + $str = $arg; + }else{ + $escapeSeq .= $arg; + } + } + if($escapeSeq=='') $escapeSeq = self::FORMAT_NORMAL; + $escapeSeq = substr($escapeSeq, 0, -1).'m'; + ///TODO:APC: Regex anything bad out from the $escapeSeq at this point. + echo(chr(27)."[{$escapeSeq}{$str}".chr(27)."[0m"); + } + + protected final function termPrint() { + $this->output(func_get_args()); + } + + protected final function termPrintln() { + $args = func_get_args(); + if(!isset($args[0])) $args[0] = ''; + $args[0] .= "\n"; + $this->output($args); + } + + protected final function readLine($prompt=null,$matches=null,$retries=2,$helpText=null){ + if($retries<0){ + throw new \Exception('Number of retries exhausted for \''.$prompt.'\''); + return null; + } + if($prompt!=null){ + echo $prompt.' : '; + } + $line = trim(fgets(STDIN)); + if(is_array($matches)){ + if(in_array($line,$matches)){ + return $line; + } else { + if($helpText!=null){ + echo $helpText."\n"; + } + $retries --; + return $this->readLine($prompt,$matches,$retries,$helpText); + } + } else if($matches!=null){ + if(preg_match($matches,$line)){ + return $line; + } else { + if($helpText!=null){ + echo $helpText."\n"; + } + $retries --; + return $this->readLine($prompt,$matches,$retries,$helpText); + } + } + return $line; + } +} \ No newline at end of file diff --git a/nano/core/cli/views/footer.php b/nano/core/cli/views/footer.php new file mode 100644 index 0000000..a0f5fa4 --- /dev/null +++ b/nano/core/cli/views/footer.php @@ -0,0 +1,5 @@ + + +************************************************** +************************************************** + diff --git a/nano/core/cli/views/header.php b/nano/core/cli/views/header.php new file mode 100644 index 0000000..db4e335 --- /dev/null +++ b/nano/core/cli/views/header.php @@ -0,0 +1,19 @@ + + _____ _ _____ __ ___ + / ____| | |_ _| /_ | / _ \ +| | | | | | ______ __ __| | | | | | +| | | | | | |______| \ \ / /| | | | | | +| |____| |____ _| |_ \ V / | |_| |_| | + \_____|______|_____| \_/ |_(_)\___/ + +Authors{ + - Christopher Beck (cwbeck@gmail.com) + - Alex Cipriani (alexcip@gmail.com) +} +Created: 12/10/2010 +Last Updated: 12/10/2010 +Version: 1.0 + +************************************************** +************************************************** + diff --git a/nano/core/config/Config.class.php b/nano/core/config/Config.class.php new file mode 100755 index 0000000..bb4627e --- /dev/null +++ b/nano/core/config/Config.class.php @@ -0,0 +1,139 @@ +addError('Exception',$e); + ///TODO:APC: Unnecessary cast. + if((bool)\ini_get('display_errors')){ + $exceptionWidget = new \project\globals\widgets\exception\Exception(); + $exceptionWidget->setParentException($e); + echo $exceptionWidget->getRenderedWidget(); + } + } + + // final public static function setDefaultShutdownHandler() { + // register_shutdown_function(__CLASS__.'::defaultShutdownHandler'); + // } + // + // public static function defaultShutdownHandler() { + // $error = error_get_last(); + // if($error!==null){ + // ob_start(); + // + // ob_flush(); + // } + // } + + /** + * Get the name of the current environment. + * @return string Current environment name. + */ + final public static function getEnvironment() { + return self::$environment; + } + + + /** + * Get the value of a configuration variable in the current environment. + * @param string $name Name of the configuration variable to retrieve. + * @return mixed Value of requested variable, or NULL if that variable does not exist. + */ + final public static function get($name) { + $env = 'project\config\environments\\'.self::$environment; + return isset($env::$config[$name])? $env::$config[$name] : null; + } + + /** + * Set the value of a configuration variable in the current environment. + * @param string $name Name of the variable to set. + * @param mixed $value Value to assign. + */ + final public static function set($name,$value) { + $env = 'project\config\environments\\'.self::$environment; + $env::$config[$name] = $value; + } + + + /** + * Get the whole configuration array for the current environment. + * @return array + */ + final public static function getConfig() { + $env = 'project\config\environments\\'.self::$environment; + return $env::$config; + } + + /** + * Method stub to be extended when needed. + * Called by nano\core\routing\Routing. + */ + public static function bootStrap(){ } +} \ No newline at end of file diff --git a/nano/core/config/PHPErrors.class.php b/nano/core/config/PHPErrors.class.php new file mode 100644 index 0000000..812c213 --- /dev/null +++ b/nano/core/config/PHPErrors.class.php @@ -0,0 +1,66 @@ +database = $databases['default']['name']; + } + return self::$instance; + } + + /** + * Function - Set Database + * @param mixed $database Database + */ + public function setDatabase($database) { + $this->database = $database; + } + + /** + * Function - Get Table + * @param mixed $name Name + * @param mixed $database=null Database=null + * @return mixed + */ + public function getTable($name,$database=null) { + $database = ($database==null)? $this->database : $database; + $newClassName = 'project\db\om\\'.$database.'\\'.$name.'Table'; + foreach($this->tableInstances as $className => $tableInstance){ + if($className==$newClassName){ + return $tableInstance; + } + } + $tableInstance = new $newClassName(); + $this->tableInstances[get_class($tableInstance)] = $tableInstance; + return $tableInstance; + } +} \ No newline at end of file diff --git a/nano/core/db/core/Database.class.php b/nano/core/db/core/Database.class.php new file mode 100755 index 0000000..99fad88 --- /dev/null +++ b/nano/core/db/core/Database.class.php @@ -0,0 +1,235 @@ +dbData = \project\config\Config::get('databases'); + if(isset($this->dbData[$name])){ + $this->dbAliases = isset($this->dbData[$name]['database_aliases'])? $this->dbData[$name]['database_aliases'] : array(); + //Set the default database to connect to on this instance. + //When changing database on the connection see 'setDefaultDatabase()' below ... + $this->defaultDatabaseName = $this->dbData[$name]['name']; + $databaseAlias = isset($this->dbAliases[$this->defaultDatabaseName])? $this->dbAliases[$this->defaultDatabaseName] : $this->defaultDatabaseName; + + switch($this->dbData[$name]['mode']){ + case self::DB_MODE_SINGLE: + //Connect to one instance. + $this->host = $this->dbData[$name]['servers'][0]['host']; + $this->port = $this->dbData[$name]['servers'][0]['port']; + $this->dbObject = new \mysqli( + 'p:'.$this->host, + $this->dbData[$name]['servers'][0]['user'], + $this->dbData[$name]['servers'][0]['pass'], + $databaseAlias, + $this->port + ); + if($this->dbObject->connect_errno=="0") { + $this->connected = true; + } else { + throw new \nano\core\exception\DatabaseException('There is no open connection to a database ('.$this->dbObject->connect_error.').'); + } + break; + case self::DB_MODE_REPLICATION: + break; + case self::DB_MODE_CLUSTER: + break; + default: + ///TODO:APC: Other modes not supported yet. + } + }else{ + throw new \nano\core\exception\DatabaseException('The requested database configuration ('.$name.') could not be found.'); + } + } + + /** + * Function - __clone + */ + final private function __clone() { } + + /** + * Set the character set to be used for the database server connection. + * @param string $charSet Chosen character set. + * @return boolean TRUE on success. Throws a DatabaseException on failure. + */ + public function setCharSet($charSet){ + if(!$this->dbObject->set_charset($charSet)){ + throw new \nano\core\exception\DatabaseException($this->dbObject->error); + return false; ///TODO:APC: This is redundant! + } + return true; + } + + /** + * Set the name of the default database to use in this instance. + * @param string $defaultDatabaseName Default Database Name + */ + public function setDefaultDatabase($defaultDatabaseName){ + if($this->defaultDatabaseName != $defaultDatabaseName){ + $databaseAlias = isset($this->dbAliases[$this->defaultDatabaseName])? $this->dbAliases[$this->defaultDatabaseName] : $this->defaultDatabaseName; + $query = 'USE `'.$databaseAlias.'`'; + \nano\core\log\Log::getInstance()->addQuery('[Database: '.$this->defaultDatabaseName.' | Host: '.$this->host.':'.$this->port.'] - '.$query); + $this->dbObject->query($query); + $this->defaultDatabaseName = $defaultDatabaseName; + } + } + + + /** + * Perform the given SELECT query. + * Synonym for get(). + * @param string $query SQL statement to execute. + * @return array An associative array of results. If the result set is empty, returns an empty array. + */ + public function select($query) { + return $this->get($query); + } + + + /** + * Perform the given INSERT query. + * + * @param string $query SQL statement to execute. + * @return mixed + */ + public function insert($query) { + if($this->execute($query)){ + $this->lastInsertId = $this->dbObject->insert_id; + return true; + } + return false; + } + + + /** + * Perform the given UPDATE query. + * + * @param string $query SQL statement to execute. + */ + public function update($query) { + return $this->execute($query); + } + + + /** + * Perform the given DELETE query. + * + * @param string $query SQL statement to execute. + */ + public function delete($query) { + return $this->execute($query); + } + + + /** + * Perform the given write query. + * Throws a DatabaseException on failure. + * @param string $query SQL statement to execute. + * @return boolean TRUE. + */ + public function execute($query) { + ///TODO:APC: This returns true, and only ever true. (Unless it's abused for a READ, in which case it will + /// return a result set!) Just a little weird... + \nano\core\log\Log::getInstance()->addQuery('[Database: '.$this->defaultDatabaseName.' | Host: '.$this->host.':'.$this->port.'] - '.$query); + if(!$this->connected){ + throw new \nano\core\exception\DatabaseException('There is no open connection to a database'); + } + $result = $this->dbObject->query($query); + if(!$result){ + throw new \nano\core\exception\DatabaseException($this->dbObject->error); + } + return $result; + } + + /** + * Perform the given read query. + * Throws a DatabaseException on failure. + * @param string $query SQL statement to execute. + * @return array An associative array of results. If the result set is empty, returns an empty array. + */ + private function get($query) { + $output = array(); + \nano\core\log\Log::getInstance()->addQuery('[Database: '.$this->defaultDatabaseName.' | Host: '.$this->host.':'.$this->port.'] - '.$query); + if(!$this->connected){ + throw new \nano\core\exception\DatabaseException('There is no open connection to a database'); + } + $result = $this->dbObject->query($query); + if(!$result){ + throw new \nano\core\exception\DatabaseException($this->dbObject->error); + } + while($row = $result->fetch_assoc()){ + $output[] = $row; + } + $result->free(); + return $output; + } + + + /** + * Get the status of this database object. + * @return boolean TRUE if currently connect, otherwise FALSE. + */ + public function isOpen() { + return $this->connected; + } + + + /** + * Get the value of the primary key assigned during the last INSERT query performed on this database object. + * IMPORTANT: The value will only be returned for queries performed using the insert() function, NOT the execute() function, + * which will otherwise perform identically. + * @return mixed Value of primary key. + */ + public function getLastInsertId() { + return $this->lastInsertId; + } +} \ No newline at end of file diff --git a/nano/core/db/core/SelectQuery.class.php b/nano/core/db/core/SelectQuery.class.php new file mode 100644 index 0000000..1232943 --- /dev/null +++ b/nano/core/db/core/SelectQuery.class.php @@ -0,0 +1,155 @@ +databaseName = $databaseName; + } + + /** + * Set the name of the database on which this query will be executed. + * @param string $databaseName Database name. + * @return \nano\core\db\core\SelectQuery $this. + */ + public function setDatabase($databaseName) { + $this->databaseName = $databaseName; + return $this; + } + + /** + * Set the name of the table on which the query will be performed. + * @param string $tableName Table name. + * @return \nano\core\db\core\SelectQuery $this. + */ + public function from($tableName) { + $this->from = $tableName; + return $this; + } + + /** + * Set the type of join to use on the query. + * @param string $joinType Join type. + */ + public function joinType($joinType) { + $this->joinType = $joinType; + } + + /** + * Set the table(s) to join to. + * @param string $tableName Name of table to join to. + * @return \nano\core\db\core\SelectQuery $this. + */ + public function join($tableName) { + $this->join = $tableName; + return $this; + } + + /** + * Set the column(s) to join on. + * @param string $on Column(s) to join on (in MySQL query syntax). + * @return \nano\core\db\core\SelectQuery $this. + */ + public function on($on) { + $this->on = $on; + return $this; + } + + /** + * Set the WHERE clause for the query. + * @param string $where WHERE clause in MySQL query syntax. + * @return \nano\core\db\core\SelectQuery $this. + */ + public function where($where) { + $this->where = $where; + return $this; + } + + /** + * Set a LIMIT for the query. + * @param int $limit + * @return \nano\core\db\core\SelectQuery $this. + */ + public function limit($limit) { + ///TODO:APC: This does not allow for the OFFSET,LIMIT syntax. Any reason? + if(preg_match('/^\d+$/',$limit)){ + $this->limit = $limit; + } else { + throw new \nano\core\exception\DatabaseException('Limit should be of type integer'); + } + return $this; + } + + /** + * Get the resulting query string. + * @return string Query string to pass to MySQL. + */ + public function getQuery(){ + $query = 'SELECT'; + $mapClassName = 'project\db\om\\'.$this->databaseName.'\map\Map'; + $selectColumns = array(); + $mapTablesToColumns = $mapClassName::$mapTablesToColumns; + $fromModel = $mapClassName::getModelNameFromTableName($this->from); + $this->objectsToHydrate[] = $fromModel; + foreach($mapTablesToColumns[$this->from] as $key => $col){ + $selectColumns[] = '`'.$this->from.'`.`'.$col.'` AS `'.$fromModel.'___'.$key.'`'; + } + ///TODO:APC: This && (and the others below) are redundant, as ''==null. + if($this->join!=''&&$this->join!=null){ + $joinModel = $mapClassName::getModelNameFromTableName($this->join); + $this->objectsToHydrate[] = $joinModel; + foreach($mapTablesToColumns[$this->join] as $key => $col){ + $selectColumns[] = '`'.$this->join.'`.`'.$col.'` AS `'.$joinModel.'___'.$key.'`'; + } + } + if(count($selectColumns)>0){ + foreach($selectColumns as $col){ + $query .= ' '.$col.','; + } + $query = substr($query,0,-1); + } + $query .= ' FROM `'.$this->from.'`'; + if($this->join!=''&&$this->join!=null){ + $query .= ' '.$this->joinType.' `'.$this->join.'` ON '.$this->on; + } + if($this->where!=''&&$this->where!=null){ + $query .= ' WHERE '.$this->where; + } + if($this->limit!=''&&$this->limit!=null){ + $query .= ' LIMIT '.$this->limit; + } + return $query; + } + + /** + * Get an array of the model objects to hydrate based on the content of the query. + * @return array Set of model class names. + */ + public function getObjectsToHydrate(){ + return $this->objectsToHydrate; + } + +} \ No newline at end of file diff --git a/nano/core/db/om/Base.class.php b/nano/core/db/om/Base.class.php new file mode 100644 index 0000000..4d302a4 --- /dev/null +++ b/nano/core/db/om/Base.class.php @@ -0,0 +1,328 @@ +referencedByCacheKeys)){ + $this->referencedByCacheKeys[] = $newCacheKey; + $memcached = \nano\core\cache\Memcached::getInstance($this->dbConfig); + $existingReferenceByCacheKeys = unserialize($memcached->get($this->getCacheReferenceKeyIdentifier())); + if(is_array($existingReferenceByCacheKeys)){ + $this->referencedByCacheKeys = array_unique(array_merge($this->referencedByCacheKeys,$existingReferenceByCacheKeys)); + $memcached->set($this->getCacheReferenceKeyIdentifier(),serialize($this->referencedByCacheKeys)); + } else { + $memcached->set($this->getCacheReferenceKeyIdentifier(),serialize($this->referencedByCacheKeys)); + } + } + } + + /** + * Function - Get Cache Reference Key Identifier + */ + protected function getCacheReferenceKeyIdentifier(){ + $cacheReferenceKeyIdentifier = 'model-cache-ref-'.$this->dbName.'-'.$this->tableName; + foreach($this->primaryKey as $key){ + $cacheReferenceKeyIdentifier .= '-'.$key.'-'.sha1($this->getValueFromKey($key)); + } + return $cacheReferenceKeyIdentifier; + } + + /** + * Function - Get Cache Reference Keys + * @return mixed + */ + public function getCacheReferenceKeys(){ + $memcached = \nano\core\cache\Memcached::getInstance($this->dbConfig); + $existingReferenceByCacheKeys = unserialize($memcached->get($this->getCacheReferenceKeyIdentifier())); + if(is_array($existingReferenceByCacheKeys)){ + return $existingReferenceByCacheKeys; + } + return array(); + } + + /** + * Function - Flush Cache Against Cache Reference Keys + */ + private function flushCacheAgainstCacheReferenceKeys(){ + $this->referencedByCacheKeys = $this->getCacheReferenceKeys(); + foreach($this->referencedByCacheKeys as $referencedByCacheKey){ + if(!$this->doCacheDelete($referencedByCacheKey)){ + throw new \nano\core\exception\CoreException('Failed to delete value from memcached key: '.$referencedByCacheKey); + } + } + } + + /** + * Function - Do Cache Delete + * @param mixed $key Key + * @param mixed $attempts Attempts + * @return mixed + */ + private function doCacheDelete($key,$attempts=5){ + $memcached = \nano\core\cache\Memcached::getInstance($this->dbConfig); + for($i=0;$i<$attempts;$i++){ + if($memcached->delete($key)){ + return true; + } elseif ($memcached->getResultCode()==$memcached->getValueFromMemcacheConstant('RES_NOTFOUND')) { + //if key not found, result therefore can't exist and assume expired, previously deleted or never existed... + return true; + } + //back off against attempts - 1000000 = 1 second. + usleep(100000 * pow(3,$i)); + } + return false; + } + + + /** + * Function - __set + * @param mixed $key Key + * @param mixed $value Value + */ + public function __set($key,$value) { + $this->data[$key] = $value; + } + + + /** + * Function - __get + * @param mixed $key Key + * @return mixed + */ + public function __get($key) { + if(!isset($this->data[$key])){ + return null; + } + $fieldInfo = $this->fields[$this->newFieldNameMap[$key]]; + if($fieldInfo['is_foreign_reference']){ + return \nano\core\db\ORM::getInstance()->getTable($fieldInfo['use_model'],$fieldInfo['use_database'])->retrieveByPk($this->data[$key]); + } + return $this->data[$key]; + } + + /** + * Function - Get Field Name From Model Key + * @param mixed $key Key + */ + private function getFieldNameFromModelKey($key){ + $fieldName = $key; + if(isset($this->fields[$fieldName])){ + if($this->fields[$fieldName]['is_foreign_reference']){ + $fieldName = $this->fields[$fieldName]['use_model']; + } + } + return $fieldName; + } + + + /** + * Function - Get Value From Key + * @param mixed $key Key + * @return mixed + */ + public function getValueFromKey($key) { + if(($key=='')||(!isset($this->data[$key]))) { + return null; + } + return $this->data[$key]; + } + + + /** + * Function - Check My Sql Special Function + * @param mixed $value Value + */ + public function checkMySqlSpecialFunction($value) { + if(preg_match('/^MYSQL_([\w\d\_\-]+)\((.*?)\)$/',$value,$matches)){ + switch($matches[1]){ + case 'NOW': + $value = date('Y-m-d H:i:s'); + //return 'NOW()'; + break; + case 'INET_ATON': + $value = ip2long($matches[2]); + //return 'INET_ATON(\''.$matches[2].'\')'; + break; + } + } + + return addslashes($value); + } + + /** + * Function - Save + */ + public function save() { + $db = \nano\core\db\core\Database::open($this->dbConfig); + $db->setDefaultDatabase($this->dbName); + if($db->isOpen()) { + //on save flush cache keys + if(\nano\core\config\Config::get('caching')==true){ + $this->flushCacheAgainstCacheReferenceKeys(); + } + return $this->insertUpdate($db); + } else { + throw new \nano\core\exception\DatabaseException('Failed to open a connection to the database...'); + } + return false; + } + + /** + * Function - Insert Update + * @param mixed $db Db + */ + private function insertUpdate($db) { + $isUpdateable = false; + $createQuery = 'INSERT INTO `'.$this->tableName.'` ('; + foreach($this->data as $key => $value){ + if(!in_array($key,$this->primaryKey)){ + $isUpdateable = true; + } + if(isset($this->newFieldNameMap[$key])){ + $createQuery .= '`'.$this->newFieldNameMap[$key].'`,'; + } + } + $createQuery = substr($createQuery,0,-1).') VALUES ('; + foreach($this->data as $key => $value){ + if(isset($this->newFieldNameMap[$key])){ + $createQuery .= '"'.$this->checkMySqlSpecialFunction($value).'",'; + } + } + $createQuery = substr($createQuery,0,-1).')'; + if($isUpdateable){ + $createQuery .= ' ON DUPLICATE KEY UPDATE '; + foreach($this->data as $key => $value){ + if((!in_array($key,$this->primaryKey))&&(isset($this->newFieldNameMap[$key]))){ + $createQuery .= '`'.$this->newFieldNameMap[$key].'`="'.$this->checkMySqlSpecialFunction($value).'",'; + } + } + $createQuery = substr($createQuery,0,-1); + } + if(!$db->insert($createQuery)){ + return false; + } + if(count($this->primaryKey)==1){ + $lastInsertId = (int)$db->getLastInsertId(); + if($lastInsertId!==0){ + $this->data[$this->primaryKey[0]] = $lastInsertId; + } + } + return true; + } + + + /** + * Function - Delete + * @return mixed + */ + public function delete() { + if(isset($this->newFieldNameMap['is_deleted'])) { + $this->data['is_deleted'] = 1; + return $this->save(); + } else { + $db = \nano\core\db\core\Database::open($this->dbConfig); + $db->setDefaultDatabase($this->dbName); + if($db->isOpen()) { + $deleteQuery = 'DELETE FROM `'.$this->tableName.'` WHERE '; + foreach($this->primaryKey as $primaryKey){ + $deleteQuery .= '`'.$primaryKey.'`="'.addslashes($this->data[$primaryKey]).'" AND '; + } + $deleteQuery = substr($deleteQuery,0,-4).'LIMIT 1'; + //on delete flush cache keys + if(\nano\core\config\Config::get('caching')==true){ + $this->flushCacheAgainstCacheReferenceKeys(); + } + return $db->delete($deleteQuery); + } else { + throw new \nano\core\exception\DatabaseException('Failed to open a connection to the database...'); + } + } + return false; + } + + /** + * Function - Get Database Config + * @return string + */ + public function getDatabaseConfig(){ + return $this->dbConfig; + } + + /** + * Function - Get Model Name + * @return mixed + */ + public function getModelName(){ + return $this->modelName; + } + + /** + * Function - Get Primary Key + * @return mixed + */ + public function getPrimaryKey(){ + return $this->primaryKey; + } + + /** + * Function - Get Database Name + * @return mixed + */ + public function getDatabaseName(){ + return $this->dbName; + } + + /** + * Function - Get Table Name + * @return mixed + */ + public function getTableName(){ + return $this->tableName; + } + + /** + * Function - Get Data Array + * @return mixed + */ + public function getDataArray(){ + return $this->data; + } + + /** + * Function - Get Field Information + * @return mixed + */ + public function getFieldInformation(){ + return $this->fields; + } +} \ No newline at end of file diff --git a/nano/core/db/om/BaseTable.class.php b/nano/core/db/om/BaseTable.class.php new file mode 100644 index 0000000..ee23bba --- /dev/null +++ b/nano/core/db/om/BaseTable.class.php @@ -0,0 +1,189 @@ +dbConfig); + $db->setDefaultDatabase($this->dbName); + if($db->isOpen()){ + return $db->select($query); + } else { + throw new \nano\core\exception\DatabaseException('Failed to open a connection to the database...'); + } + } + + /** + * Function - Do Select + * @param mixed $query Query + * @param $cacheKey=null Cache Key=null + * @param $cacheExpires=\nano\core\cache\Memcached::EXPIRES_30MINS Cache Expires=\nano\core\cache\Memcached::EXPIRES_30MINS + * @return mixed + */ + public function doSelect($query, $cacheKey=null, $cacheExpires=\nano\core\cache\Memcached::EXPIRES_30MINS) { + if($cacheKey!==null){ + if(\nano\core\config\Config::get('caching')==true){ + $memcached = \nano\core\cache\Memcached::getInstance($this->dbConfig); + \nano\core\log\Log::getInstance()->addCachedInfo('Select using Cache Key: '.$memcached->getKeyPrefix().'-'.$cacheKey.' (Expires: '.$cacheExpires.')'); + if(false!==($objs = $memcached->get($cacheKey))){ + \nano\core\log\Log::getInstance()->addCachedInfo('Found using Cache Key: '.$memcached->getKeyPrefix().'-'.$cacheKey.' (Expires: '.$cacheExpires.')'); + return unserialize($objs); + } + } + } + $db = \nano\core\db\core\Database::open($this->dbConfig); + $db->setDefaultDatabase($this->dbName); + if($db->isOpen()){ + $objs = array(); + if(is_object($query)){ + if($query instanceof \nano\core\db\core\SelectQuery) { + $query->setDatabase($this->dbName); + $query = $query->getQuery(); + $results = $db->select($query); + foreach($results as $result){ + $obj = array(); + $modelNames = array(); + foreach($result as $key => $value){ + if(preg_match('/^(.+?)[\_]{3}(.+)/',$key,$matches)){ + $modelName = $matches[1]; + $columnName = $matches[2]; + $hydratedObject = 'project\db\om\\'.$this->dbName.'\\'.$modelName; + if(!array_key_exists($modelName,$obj)){ + $modelNames[] = $modelName; + $obj[$modelName] = new $hydratedObject(); + } + $obj[$modelName]->{$columnName} = $value; + } + } + foreach($modelNames as $modelName){ + if($cacheKey!==null) { + if(\nano\core\config\Config::get('caching')==true){ + $obj[$modelName]->addCacheReferenceKey($cacheKey); + } + } + } + $objs[] = $obj; + } + } else { + throw new \nano\core\exception\CoreException('The $query object passed into doSelect(...) was not and instance of \nano\core\db\core\SelectQuery'); + } + } else { + $hydratedObject = 'project\db\om\\'.$this->dbName.'\\'.$this->modelName; + $results = $db->select($query); + foreach($results as $result){ + $obj = new $hydratedObject(); + foreach($result as $key => $value){ + $fieldName = $key; + if(isset($this->fields[$fieldName])){ + if($this->fields[$fieldName]['is_foreign_reference']){ + $fieldName = $this->fields[$fieldName]['use_model']; + } + } + $obj->{$fieldName} = $value; + } + if($cacheKey!==null) { + if(\nano\core\config\Config::get('caching')==true){ + $obj->addCacheReferenceKey($cacheKey); + } + } + $objs[] = $obj; + } + } + if($cacheKey!==null) { + if(\nano\core\config\Config::get('caching')==true){ + \nano\core\log\Log::getInstance()->addCachedInfo('Set using Cache Key: '.$memcached->getKeyPrefix().'-'.$cacheKey.' (Expires: '.$cacheExpires.')'); + $memcached->set($cacheKey,serialize($objs),$cacheExpires); + } + } + return $objs; + } else { + throw new \nano\core\exception\DatabaseException('Failed to open a connection to the database...'); + } + } + + /** + * Function - Truncate Table + */ + public function truncateTable(){ + $db = \nano\core\db\core\Database::open($this->dbConfig); + $db->setDefaultDatabase($this->dbName); + if($db->isOpen()){ + $db->execute('TRUNCATE TABLE `'.$this->tableName.'`'); + } else { + throw new \nano\core\exception\DatabaseException('Failed to open a connection to the database...'); + } + } + + /** + * Function - Get Model Name + * @return mixed + */ + public function getModelName(){ + return $this->modelName; + } + + /** + * Function - Get Primary Key + * @return mixed + */ + public function getPrimaryKey(){ + return $this->primaryKey; + } + + /** + * Function - Get Database Name + * @return mixed + */ + public function getDatabaseName(){ + return $this->dbName; + } + + /** + * Function - Get Table Name + * @return mixed + */ + public function getTableName(){ + return $this->tableName; + } + + /** + * Function - Get Field Information + * @return mixed + */ + public function getFieldInformation(){ + return $this->fields; + } + + /** + * Function - Get New Field Name Map Information + * @return mixed + */ + public function getNewFieldNameMapInformation(){ + return $this->newFieldNameMap; + } +} \ No newline at end of file diff --git a/nano/core/exception/CoreException.class.php b/nano/core/exception/CoreException.class.php new file mode 100644 index 0000000..6a31e75 --- /dev/null +++ b/nano/core/exception/CoreException.class.php @@ -0,0 +1,23 @@ +addError('CoreException',$this); + } +} \ No newline at end of file diff --git a/nano/core/exception/DatabaseException.class.php b/nano/core/exception/DatabaseException.class.php new file mode 100644 index 0000000..bf3899e --- /dev/null +++ b/nano/core/exception/DatabaseException.class.php @@ -0,0 +1,23 @@ +addError('DatabaseException',$this); + } +} \ No newline at end of file diff --git a/nano/core/exception/I18nException.class.php b/nano/core/exception/I18nException.class.php new file mode 100644 index 0000000..f72047e --- /dev/null +++ b/nano/core/exception/I18nException.class.php @@ -0,0 +1,23 @@ +addError('I18nException',$this); + } +} \ No newline at end of file diff --git a/nano/core/exception/ValidationException.class.php b/nano/core/exception/ValidationException.class.php new file mode 100644 index 0000000..bde9b8c --- /dev/null +++ b/nano/core/exception/ValidationException.class.php @@ -0,0 +1,25 @@ +addError('ValidationException',$this,\nano\core\log\Log::PRIORITY_LEVEL_DEVLOG); + } +} \ No newline at end of file diff --git a/nano/core/helpers/FileSystem.class.php b/nano/core/helpers/FileSystem.class.php new file mode 100644 index 0000000..c76f89b --- /dev/null +++ b/nano/core/helpers/FileSystem.class.php @@ -0,0 +1,111 @@ + $fileNames, + 'folders' => $folderNames + ); + } + + /** + * Get all the folders (non-recursively) in the specified directory. + * @param string $dir Directory to scan. + * @return array An array of all directories found, with each element prefixed by $dir. + */ + public static function getFoldersIn($dir) { + $folderScan = scandir($dir); + $folders = array(); + foreach($folderScan as $folderName){ + if(is_dir($dir.$folderName)){ + if(!preg_match('/^\./',$folderName)){ + $folders[$folderName] = $dir.$folderName.'/'; + } + } + } + return $folders; + } + + /** + * Get all the files in the specified directory. + * @param string $dir Directory to scan. + * @return array An array of all files found, with each element prefixed by $dir. + */ + public static function getFilesIn($dir) { + $folderScan = scandir($dir); + $files = array(); + foreach($folderScan as $fileName){ + if(!is_dir($dir.$fileName)){ + if(!preg_match('/^\./',$fileName)){ + $files[$fileName] = $dir.$fileName; + } + } + } + return $files; + } + + /** + * Get all the files in the specified directory and any below it. + * @param string $dir Directory to scan. + * @param array $filesUnder An option array to append the results to. Used internally for recursion. + * @return array An array of all files found. Each element will be prefixed by $dir. + */ + public static function getFilesUnder($dir,$filesUnder = array()) { + foreach(self::getFoldersIn($dir) as $newDir){ + $newFiles = self::getFilesIn($newDir); + foreach($newFiles as $newFile){ + $filesUnder[] = $newFile; + } + $filesUnder = self::getFilesUnder($newDir,$filesUnder); + } + return $filesUnder; + } +} \ No newline at end of file diff --git a/nano/core/i18n/I18n.class.php b/nano/core/i18n/I18n.class.php new file mode 100644 index 0000000..e8cb771 --- /dev/null +++ b/nano/core/i18n/I18n.class.php @@ -0,0 +1,193 @@ +instanceDataStore = array(); + } + + /** + * Function - __clone + */ + final private function __clone() { } + + /** + * Returns the singleton instance of the I18n class. + * @return \I18n Class instance. + */ + public static function getInstance() { + if(self::$instance==null){ + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Set the language to be used for this page. + * The specified $code must be in the form L_R, where 'L' is a two- or three-letter language tag, + * comprised of lower case letters only, and 'R' is a two- or three-letter region tag, + * comprised of upper case letters only. + * + * @param string $code Language code as described above. + */ + public function setLanguage($code) { + if(preg_match('/^[a-z]{2,3}\_[A-Z]{2,3}$/',$code)){ + $this->languageCode = $code; + } else { + ///TODO:APC: Why does this not throw an I18nException? + throw new \nano\core\exception\CoreException('Could not set language from code \''.$code.'\''); + } + } + + /** + * Get thge language currently in use. + * @return string Language code. + */ + public function getLanguage() { + return $this->languageCode; + } + + /** + * Function - Variable Replacement + * @param mixed $str Str + * @param mixed $replacements=array() Replacements=array() + */ + private function variableReplacement($str,$replacements=array()){ + foreach($replacements as $key => $value){ + $str = str_replace($key,$value,$str); + } + return $str; + } + + /** + * Get an internationalised string in the currently selected language. + * If a string for the specified key and current language is not found in the language table, + * $defaultValue will be returned instead. + * $replacements is an optional associative array of strings to be replaced in the output. + * For example, if $replacements['foo']=='bar', all instances of 'foo' in the output will + * be replaced with 'bar' at the last stage of the function before returning. Replacements will + * NOT be committed to either the cache or the database. + * + * Config options: i18n_marker, i18n_overwrite, i18n_populate. + * + * @param string $key String key. + * @param string $defaultValue + * @param array $replacements + * @return string + */ + public function get($key,$defaultValue='',$replacements=array()) { + + //String key must not be empty. + if(($key===null)||($key=='')){ + throw new \nano\core\exception\I18nException('Key must not be empty'); + } + + //Check that language code has been set. + if($this->languageCode==null){ + throw new \nano\core\exception\I18nException('Language code must be set'); + } + + $populate = (\nano\core\config\Config::get('i18n_populate')==true)? true : false; + $overWrite = (\nano\core\config\Config::get('i18n_overwrite')==true)? true : false; + + //Attempt to retrieve the required value from the data store in this instance. + $value = isset($this->instanceDataStore[$key])? $this->instanceDataStore[$key] : null; + + //If the value is found, no need to proceed unless in overwrite mode. + if($value===null || $overWrite){ + //Flag specifying whether Memecache should be updated with a new value for this key. + $cacheUpdate = false; + $cacheKey = 'nanophp-i18n-'.$this->languageCode.'-'.md5(\nano\core\routing\Routing::getInstance()->getUrl()); + $memcacheDataStore = array(); + //If caching is disabled in the project configuration, skip this part... + if(\nano\core\config\Config::get('caching')==true){ + //If the value has not been found in the local data store, try to fetch from Memcache based on page URL. + if($value===null){ + $memcacheDataStore = unserialize(\nano\core\cache\Memcached::getInstance()->get($cacheKey)); + $memcacheDataStore = is_array($memcacheDataStore)? $memcacheDataStore : array(); + $value = isset($memcacheDataStore[$key])? $memcacheDataStore[$key] : null; + if($value!==null){ + //Value has been found in Memcache. Merge all memcache data into the local data store. + ///TODO:APC: Why set $cacheUpdate here? It seems like a bit of a hack... + $cacheUpdate = true; + $this->instanceDataStore = array_merge($this->instanceDataStore,$memcacheDataStore); + //As value has been found in the cache, it must exist in the database, so just return unless in overwrite mode. + if(!$overWrite){ + if(\nano\core\config\Config::get('i18n_marker')==true){ + return '{{ '.$this->variableReplacement($value,$replacements).' }}'; + } + return $this->variableReplacement($value,$replacements); + } + } + } + } + + //If the value has still not been found, or populate or overwrite options are set, we need to refer to the database. + if($value===null || $populate || $overWrite){ + $databases = \nano\core\config\Config::get('databases'); + $defaultDatabase = $databases['default']['name']; + $language = \nano\core\db\ORM::getInstance()->getTable('Language',$defaultDatabase)->retrieveByPk($this->languageCode,$key); + if($language==null || $overWrite){ + //Key was not found in the database. Use the default value. + if($populate || $overWrite){ + foreach(\project\config\Config::$language as $supportedLanguageCode => $supportedLanguageConfig){ + try { + $language = \nano\core\db\ORM::getInstance()->getTable('Language',$defaultDatabase)->retrieveByPk($supportedLanguageCode,$key); + if($language==null){ + $languageModelClassName = 'project\db\om\\'.$defaultDatabase.'\Language'; + $language = new $languageModelClassName(); + $language->code = $supportedLanguageCode; + $language->key = $key; + $language->value = $defaultValue; + $language->save(); + } elseif($overWrite) { + $language->value = $defaultValue; + $language->save(); + } + $cacheUpdate = true; + $this->instanceDataStore[$language->key] = $language->value; + } catch (\Exception $e) { + //Error automatically logged and displayed. + } + } + + } + } else { + //Key was found in the database. Add the value to the local store, and mark cache to be updated. + $cacheUpdate = true; + $this->instanceDataStore[$language->key] = $language->value; + } + } + + if($cacheUpdate){ + if(\nano\core\config\Config::get('caching')==true){ + if((!empty($this->instanceDataStore)) && ($memcacheDataStore!=$this->instanceDataStore)) { + \nano\core\cache\Memcached::getInstance()->set($cacheKey,serialize($this->instanceDataStore),\nano\core\cache\Memcached::EXPIRES_30MINS); + } + } + } + + } + + if(\nano\core\config\Config::get('i18n_marker')==true){ + return isset($this->instanceDataStore[$key])? '{{ '.$this->variableReplacement($this->instanceDataStore[$key],$replacements).' }}' : '{{ }}'; + } + + return isset($this->instanceDataStore[$key])? $this->variableReplacement($this->instanceDataStore[$key],$replacements) : null; + } +} \ No newline at end of file diff --git a/nano/core/log/Log.class.php b/nano/core/log/Log.class.php new file mode 100755 index 0000000..27688f6 --- /dev/null +++ b/nano/core/log/Log.class.php @@ -0,0 +1,221 @@ +queries = array(); + $this->errors = array(); + $this->cachedInfo = array(); + switch(\project\config\Config::get('logging_priority')){ + case self::PRIORITY_LEVEL_LOGFILE: + $this->errorPriorityLevel = self::PRIORITY_LEVEL_LOGFILE; + break; + case self::PRIORITY_LEVEL_EMAIL: + $this->errorPriorityLevel = self::PRIORITY_LEVEL_EMAIL; + break; + case self::PRIORITY_LEVEL_SMS: + $this->errorPriorityLevel = self::PRIORITY_LEVEL_SMS; + break; + default: + $this->errorPriorityLevel = self::PRIORITY_LEVEL_DEVLOG; + break; + } + } + + /** + * Function - __clone + */ + final private function __clone() { } + + /** + * Get a singleton instance of the logger. + */ + public static function getInstance() { + if(self::$instance==null){ + self::$instance = new self(); + self::$instance->startTimer(); + } + return self::$instance; + } + + /** + * Start the execution timer. + */ + public function startTimer() { + ///TODO:APC: Is there any reason not to just use microtime(true)? + $time = microtime(); + $time = explode(' ', $time); + $this->startTime = $time[1] + $time[0]; + } + + /** + * Get the time since the last call to startTimer(). + * @return float Time in milliseconds. + */ + public function getExecuteTime() { + $time = microtime(); + $time = explode(' ', $time); + $time = $time[1] + $time[0]; + return ($time - $this->startTime)*1000; + } + + /** + * Add an SQL query to the log. + * @param string $query SQL Statement. + */ + public function addQuery($query) { + $this->queries[] = $query; + } + + /** + * Get the SQL queries that have been logged. + * @return array Array of SQL statement strings. + */ + public function getQueries() { + return $this->queries; + } + + /** + * Add information to the cache log. + * @param string $info Human-readable information string. + */ + public function addCachedInfo($info) { + $this->cachedInfo[] = $info; + } + + /** + * Get the cache log. + * @return array Array of strings in the cache log. + */ + public function getCachedInfos() { + return $this->cachedInfo; + } + + /** + * Set the default error priority level. + * Errors logged that have not been assigned their own priority will use this default. + * @param int $errorPriorityLevel Default priority level. + */ + public function setErrorPriorityLevel($errorPriorityLevel) { + $this->errorPriorityLevel = $errorPriorityLevel; + } + + /** + * Add an error to the error log. + * If $fileName is not specified, the name of the log file will be determined based on the + * $type argument. If the $filePath is not specified, NanoPHP will attempt to use the path set + * in the 'logging_path' config variable, or if that is not specified, the system's temporary + * directory. + * @param string $type Human-readable string defining type of error. + * @param mixed $obj An \Exception object related to the error, or human-readable string describing the error. + * @param int $priority Error priority level. If not specified, the class default will be used. + * @param string $fileName Name of log file to write to. + * @param string $filePath Path of log file file to write to. + */ + public function addError($type,$obj,$priority=null,$fileName=null,$filePath=null) { + $stackTrace = null; + if($obj instanceof \Exception){ + $message = $obj->getMessage(); + $stackTrace = 'Thrown exception of type '.$type.' in '.$obj->getFile().' on line #'.$obj->getLine()."\n\n"; + $stackTrace .= $obj->getTraceAsString(); + } else { + $message = $obj; + } + $priority = ($priority===null)? $this->errorPriorityLevel : $priority; + switch($priority){ + case self::PRIORITY_LEVEL_LOGFILE: + $this->appendToLogFile($type,$message,$stackTrace,$fileName,$filePath); + break; + case self::PRIORITY_LEVEL_EMAIL: + $this->appendToLogFile($type,$message,$stackTrace,$fileName,$filePath); + $this->sendImmediatelyViaEmail($type,$message,$stackTrace); + break; + } + $this->errors[] = array( + 'type' => $type, + 'message' => $message, + 'stackTrace' => $stackTrace + ); + } + + /** + * Get the error log. + * @return array Array of strings in the error log. + */ + public function getErrors() { + return $this->errors; + } + + /** + * Append the given message to a log file. + * @param string $type Human-readable string defining type of error. + * @param string $message Human-readable message giving details of the error. + * @param string $stackTrace + * @param string $fileName Name of log file to write to. + * @param string $filePath Path of log file file to write to. + */ + private function appendToLogFile($type,$message,$stackTrace=null,$fileName=null,$filePath=null) { + $stackTrace = ($stackTrace==null)? 'No stack trace given...' : $stackTrace; + if($fileName==null){ + $fileName = preg_replace('/[^\w\d]+/','-',$type).'.log'; + } + if($filePath===null){ + $filePath = \project\config\Config::get('logging_path'); + if($filePath===null){ + $filePath = sys_get_temp_dir(); + } + } + + if(preg_match('/\/$/',$filePath)){ + $filePath = $filePath.'nanophp/'; + } else { + $filePath = $filePath.'/nanophp/'; + } + if(!file_exists($filePath)){ + mkdir($filePath,0777,true); + } + file_put_contents($filePath.$fileName,date('Y-m-d H:i:s').':-:'.$type.':-:'.$message.':-:'.$stackTrace.':-:'."\n",FILE_APPEND); + } + + /** + * Function - Send Immediately Via Email + * @param mixed $type Type + * @param mixed $message Message + * @param mixed $stackTrace Stack Trace + */ + private function sendImmediatelyViaEmail($type,$message,$stackTrace) { + $emails = \project\config\Config::get('logging_send_to'); + if(is_array($emails)){ + foreach($emails as $email){ + $domain = \nano\core\routing\Routing::getInstance()->getDomain(); + $headers = 'From: exception@'.$domain."\r\n".'Reply-To: exception@'.$domain."\r\n".'X-Mailer: PHP/'.phpversion(); + mail($email, $type.' - '.$message, 'Type: '.$type."\n\nMessage: ".$message."\n\n".$stackTrace, $headers); + } + } + } +} \ No newline at end of file diff --git a/nano/core/page/Page.class.php b/nano/core/page/Page.class.php new file mode 100644 index 0000000..612aaab --- /dev/null +++ b/nano/core/page/Page.class.php @@ -0,0 +1,133 @@ +routing = \nano\core\routing\Routing::getInstance(); + $this->preLoad($this->routing); + } + + /** + * Function - __set + * @param mixed $name Name + * @param mixed $value Value + */ + final public function __set($name, $value) { + $this->data[$name] = $value; + } + + //Must return a reference to the object saved in order for arrays and objects to exhibit expected behaviour, e.g. $foo[] = 'kung'. + /** + * Function - &__get + * @param mixed $name Name + * @return mixed Value + */ + final public function &__get($name) { + return $this->data[$name]; + } + + /** + * Destructor. + * Calls render() on the page. + */ + final public function __destruct() { + $this->render(); + } + + /** + * Render the page. + * Normally called by the page's destructor. + * Calls preExecute(), execute(), postExecute(), and preRender(), before passing the + * template name and page data to \nano\core\view\View::load(), and calling postRender() + * and postOutput(). + */ + final public function render() { + if(!$this->isRendered){ + $this->isRendered = true; + $this->preExecute($this->routing); + $this->templateToRender = $this->execute($this->routing); + $this->postExecute($this->routing); + $this->data['header'] = isset($this->data['header'])? $this->data['header'] : ''; + $this->preRender($this->routing); + \nano\core\view\View::load($this->templateToRender,$this->data); + $this->postRender($this->routing); + $this->postOutput($this->routing); + } + } + + /** + * Abstract method called by the page constructor. + * @param \nano\core\routing\Routing $routing Shared routing instance. + */ + protected function preLoad(\nano\core\routing\Routing $routing){ + + } + + /** + * Abstract method called immediately on the first call to render(). + * @param \nano\core\routing\Routing $routing Shared routing instance. + */ + protected function preExecute(\nano\core\routing\Routing $routing){ + + } + + /** + * Abstract method called by render() following preExecute(). + * When overridden, this should return the full path of the Twig template to render. + * @param \nano\core\routing\Routing $routing Shared routing instance. + */ + protected function execute(\nano\core\routing\Routing $routing){ + + } + + /** + * Abstract method called by render() following execute(). + * @param \nano\core\routing\Routing $routing Shared routing instance. + */ + protected function postExecute(\nano\core\routing\Routing $routing){ + + } + + /** + * Abstract method called by render() prior to rendering the Twig template. + * @param \nano\core\routing\Routing $routing Shared routing instance. + */ + protected function preRender(\nano\core\routing\Routing $routing){ + + } + + /** + * Abstract method called by render() after the Twig template has been rendered. + * @param \nano\core\routing\Routing $routing Shared routing instance. + */ + protected function postRender(\nano\core\routing\Routing $routing){ + + } + + /** + * Abstract method called by render() prior to that function returning. + * @param \nano\core\routing\Routing $routing Shared routing instance. + */ + protected function postOutput(\nano\core\routing\Routing $routing){ + + } + +} \ No newline at end of file diff --git a/nano/core/routing/Routing.class.php b/nano/core/routing/Routing.class.php new file mode 100755 index 0000000..b0075f7 --- /dev/null +++ b/nano/core/routing/Routing.class.php @@ -0,0 +1,312 @@ +customConfigClassName = 'project\apps\\'.$_SERVER['PROJECT_APP'].'\Config'; + $this->customRoutingClassName = 'project\apps\\'.$_SERVER['PROJECT_APP'].'\Routing'; + } + + /** + * Function - __clone + */ + final private function __clone() { } + + /** + * Function - Get Instance + */ + public static function getInstance() { + if(self::$instance==null){ + self::$instance = new self(); + self::$instance->url = self::$instance->getIncomingUrl(); + self::$instance->parseUrl(); + self::$instance->request = \nano\core\web\Request::getInstance(); + self::$instance->response = \nano\core\web\Response::getInstance(); + self::$instance->response->expireOnceOnlyCookies(); + //configure any installed libraries to environmental settings + \nano\core\autoloader\Autoloader::configure(); + //configure using project specific bootstrap method + $configClassName = self::$instance->customConfigClassName; + $configClassName::bootStrap(); + self::$instance->setLanguage(); + //load the page which matches the routing profile + if(!self::$instance->load()){ + new \project\globals\pages\page_not_found\PageNotFound(); + } + } + return self::$instance; + } + + /** + * Function - Set Language + * @param mixed $code=null Code=null + */ + private function setLanguage($code=null) { + if($code==null){ + //attempt to set it from the browser + if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])){ + if(preg_match('/([a-z]{2})[\_\-]{1}([a-z]{2,3})/i',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches)){ + $code = $matches[1].'_'.$matches[2]; + } + } + if(isset($_COOKIE['nano_language'])){ + $code = $_COOKIE['nano_language']; + } + } + if($code!=null){ + $configClassName = $this->customConfigClassName; + $configLanguages = $configClassName::$language; + foreach($configLanguages as $supportedLanguageCode => $supportedLanguageConfig){ + if(preg_match('/'.$supportedLanguageCode.'/i',$code)){ + \nano\core\i18n\I18n::getInstance()->setLanguage($supportedLanguageCode); + } + } + } + } + + /** + * Function - Load + */ + private function load() { + $routingClassName = $this->customRoutingClassName; + $routing = $routingClassName::$routing; + foreach($routing as $key => $route){ + $isLoadable = true; + if(isset($route['domain_prefix'])){ + $configClassName = $this->customConfigClassName; + $environmentDomain = $configClassName::$environments[\nano\core\config\Config::getEnvironment()]; + $environmentDomain = preg_replace('/(\.)|(\-)/', '\\\$1', $environmentDomain); + if(!preg_match('/'.$route['domain_prefix'].$environmentDomain.'$/', $this->domain)){ + $isLoadable = false; + } + } + $url = $route['url']; + $requirements = isset($route['requirements']) ? $route['requirements'] : array(); + $class = $route['class']; + if($this->matches($url, $requirements) && $isLoadable){ + $this->routeKey = $key; + if(isset($route['try_class'])){ + $route['try_class'] = preg_replace('/\{\{\s?(.+?)\s?\}\}/e','$_GET[\'\1\']',$route['try_class']); + if(\nano\core\autoloader\Autoloader::isFile($route['try_class'])){ + $class = $route['try_class']; + } + } + if(isset($route['get_inject'])){ + foreach($route['get_inject'] as $key => $value){ + $_GET[$key] = $value; + } + } + if(isset($route['post_inject'])){ + foreach($route['post_inject'] as $key => $value){ + $_POST[$key] = $value; + } + } + if(isset($_SERVER['HTTP_X_REQUESTED_WITH'])){ + $this->isAjax = true; + } + new $class(); + return true; + } + } + return false; + } + + /** + * Function - Get Incoming Url + */ + private function getIncomingUrl() { + $url = 'http'; + $_SERVER['HTTPS'] = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : ''; + if($_SERVER['HTTPS']=='on'){ + $url .= 's'; + } + $url .= '://'.$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"]; + return $url; + } + + /** + * Function - Matches + * @param mixed $pattern Pattern + * @param mixed $requirements=array() Requirements=array() + */ + private function matches($pattern,$requirements=array()) { + //replace slug with named regex + $pattern = preg_replace('/\//','\/',$pattern); + $pattern = preg_replace('/\/\:([\w]+)/','/(?<\1>[^\/]+)',$pattern); + $pattern = '/^(?\/[a-z]{2,3}\_[A-Z]{2,3})?(?\/ajax)?'.$pattern.'$/'; + if(preg_match($pattern,$this->path,$matches)){ + $this->pathPrefix = ''; + $matches['language'] = isset($matches['language'])? $matches['language'] : null; + if($matches['language']!=null){ + $this->pathPrefix .= $matches['language']; + $isoLang = substr($matches['language'],1); + $this->setLanguage($isoLang); + setcookie('nano_language',$isoLang,0,'/'); + } + $matches['ajax'] = isset($matches['ajax'])? $matches['ajax'] : ''; + if($matches['ajax']=='/ajax'){ + $this->pathPrefix .= '/ajax'; + $this->isAjax = true; + } + unset($matches['ajax']); //remove ajax from get var as not needed + unset($matches['language']); //remove language from get var as not needed + unset($matches[0]); //remove first match + foreach($matches as $key => $match){ + if(intval($key)==0){ + if(isset($requirements[$key])){ + if(!preg_match('/^'.$requirements[$key].'$/',$match)){ + return false; + } + } + $_GET[$key] = $match; + } + } + return true; + } + return false; + } + + /** + * Function - Parse Url + */ + private function parseUrl() { + if(preg_match('/^(?([\d\w]+)):\/\/(?[^\/]+)[\/]?+(?[^\?\&\#]+)?+(?.*?)$/i',$this->url,$matches)){ + $this->protocol = $matches['protocol']; + $this->domain = $matches['domain']; + $this->path = '/'.$matches['path']; + } + } + + /** + * Function - Is Ajax + * @return mixed + */ + public function isAjax() { + return $this->isAjax; + } + + /** + * Function - Get Url + * @return mixed + */ + public function getUrl() { + return $this->url; + } + + /** + * Function - Get Url For Param Replacement + * @param mixed $paramKey Param Key + * @param mixed $params Params + * @return mixed + */ + public static function getUrlForParamReplacement($paramKey,$params){ + if(array_key_exists($paramKey,$params)){ + return '/'.$params[$paramKey]; + } + return ''; + } + + /** + * Function - Get Url For + * @param mixed $routeKey Route Key + * @param mixed $params=array() Params=array() + * @param mixed $absolute=false Absolute=false + * @return mixed + */ + public function getUrlFor($routeKey,$params=array(),$absolute=false) { + $routingClassName = $this->customRoutingClassName; + if(isset($routingClassName::$routing[$routeKey])){ + $url = isset($routingClassName::$routing[$routeKey]['url_for'])? $routingClassName::$routing[$routeKey]['url_for'] : $routingClassName::$routing[$routeKey]['url']; + $url = preg_replace('/\/:([^\/]+)/e','self::getUrlForParamReplacement(\'\\1\',$params)',$url); + if($absolute){ + return $this->protocol.'://'.$this->domain.$url; + } + return $url; + } + return null; + } + + /** + * Function - Get Protocol + * @return mixed + */ + public function getProtocol() { + return $this->protocol; + } + + /** + * Function - Get Domain + * @return mixed + */ + public function getDomain() { + return $this->domain; + } + + /** + * Function - Get Path + * @return mixed + */ + public function getPath() { + return $this->path; + } + + /** + * Function - Get Path Prefix + * @return mixed + */ + public function getPathPrefix() { + return $this->pathPrefix; + } + + /** + * Function - Get Route Key + * @return mixed + */ + public function getRouteKey() { + return $this->routeKey; + } + + /** + * Function - Get Request Object + * @return \nano\core\web\Request + */ + public function getRequest() { + return $this->request; + } + + /** + * Function - Get Response Object + * @return \nano\core\web\Response + */ + public function getResponse() { + return $this->response; + } + +} \ No newline at end of file diff --git a/nano/core/session/Session.class.php b/nano/core/session/Session.class.php new file mode 100644 index 0000000..2863941 --- /dev/null +++ b/nano/core/session/Session.class.php @@ -0,0 +1,134 @@ +render(). + * @param string $path Path to template. If 'twig' is found in the path, Twig will be used for rendering. + * @param array $objs Array of variables to be passed to the rendering engine. + * @param $return If TRUE, the rendered template will be returned; otherwise, it will be output directly. + * @return mixed Template or void. See $return. + */ + public static function load($path, array $objs=array(), $return=false) { + if($path!=null&&$path!=false){ + ///TODO:APC: Any reason not to use the much faster stripos()? + if(preg_match('/\.twig$/',$path)){ + //render using twig... + return self::twigRender($path, $objs, (bool)$return); + } else { + //render using default nano renderer + return self::nanoRender($path, $objs, (bool)$return); + } + } + } + + /** + * Function - Twig Render + * @param mixed $path Path + * @param $objs Objs + * @param $return Return + * @return mixed + */ + private static function twigRender($path, $objs, $return){ + $twig = \project\lib\twig\Twig::getInstance(); + $twigTemplate = $twig->loadTemplate($path); + $contents = $twigTemplate->render($objs); + unset($objs); + if($return){ + return $contents; + } + echo $contents; + return true; + } + + /** + * Function - Nano Render + * @param mixed $path Path + * @param $objs Objs + * @param $return Return + */ + private static function nanoRender($path, $objs, $return){ + foreach($objs as $key => $obj){ + ${$key} = $obj; + } + unset($objs); + if($return){ + ob_start(); + include $_SERVER['SCRIPTS_LOAD_FROM'].$path; + $contents = ob_get_contents(); + ob_end_clean(); + return $contents; + } + include $_SERVER['SCRIPTS_LOAD_FROM'].$path; + return true; + } +} \ No newline at end of file diff --git a/nano/core/web/Request.class.php b/nano/core/web/Request.class.php new file mode 100644 index 0000000..c82dfc2 --- /dev/null +++ b/nano/core/web/Request.class.php @@ -0,0 +1,111 @@ +getHeader('REQUEST_METHOD'))=='POST')? true : false; + } + + /** + * Check whether the request method is PUT. + * @return bool TRUE if the PUT method was used, otherwise FALSE. + */ + public function isPut(){ + return (strtoupper($this->getHeader('REQUEST_METHOD'))=='PUT')? true : false; + } + + /** + * Check whether the request method is DELETE. + * @return bool TRUE if the DELETE method was used, otherwise FALSE. + */ + public function isDelete(){ + return (strtoupper($this->getHeader('REQUEST_METHOD'))=='DELETE')? true : false; + } +} \ No newline at end of file diff --git a/nano/core/web/Response.class.php b/nano/core/web/Response.class.php new file mode 100644 index 0000000..95707ee --- /dev/null +++ b/nano/core/web/Response.class.php @@ -0,0 +1,135 @@ + $value){ + if(preg_match('/^nano\_ooc\_/',$key)){ + if($saveBeforeExpire){ + $this->onceOnlyCookies[$key] = $value; + } + //expire only once cookies + $this->setCookie($key,'',time()-3600,'/'); + } + } + } + } + + /** + * Set a once only cookie. + * @param string $name Name of the cookie. Note that for transmission, 'nano_ooc_' will automatically be prepended. + * @param mixed $value Value of the cookie. This may be any variable/object, and will be serialized for transmission. + */ + public function setOnceOnlyCookie($name,$value='') { + $this->onceOnlyCookies['nano_ooc_'.$name] = htmlentities(serialize($value),ENT_QUOTES,'UTF-8'); + } + + /** + * Set a cookie to be sent in the HTTP headers. + * The format of arguments and their default values are identical to the PHP inbuilt function + * of the same name, with the exception that the default $path is '/', i.e. the whole domain. + * See {@link http://php.net/manual/en/function.setcookie.php } for a more detailed description of the parameters. + * @param string $name The name of the cookie. + * @param string $value The value of the cookie. + * @param int $expire Expiry time, as a UNIX timestamp. If 0, the cookie will expire at the end of the session. + * @param string $path The path on the server in which the cookie will be available on. + * @param string $domain The domain that the cookie is available to. + * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client. + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol, not via client-side scripts. + */ + public function setCookie($name, $value='', $expire=0, $path='/', $domain=null, $secure=false, $httponly=false) { + $this->cookies[$name] = array( + 'value' => $value, + 'expire' => $expire, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httponly, + ); + setcookie($name,$value,$expire,$path,$domain,$secure,$httponly); + } + + /** + * Set the HTTP status code and protocol version to be transmitted with the response. + * @param int $code HTTP status code. Default 200 (OK). + * @param string $version HTTP protocol version. Default 1.0. + */ + public function setHttpStatus($code=200,$version='1.0') { + header('HTTP/'.$version.' '.$code); + $this->setHttpHeader('Status',$code); + } + + /** + * Set an HTTP header. + * @param string $name Name of the header. + * @param string $value Value to assign. + */ + public function setHttpHeader($name, $value) { + header($name.': '.$value); + } + + /** + * Redirect by setting the HTTP Location header. + * The HTTP response code will also be set to '302 Found' unless it has already been modified. + * @param mixed $url URL to redirect to. If null or omitted, the current URL will be used. + */ + public function pageRedirect($url=null) { + $url = ($url==null)? $_SERVER['REQUEST_URI'] : $url; + ob_start(); + $this->expireOnceOnlyCookies(false); + foreach($this->onceOnlyCookies as $key => $value){ + setcookie($key,$value,0,'/'); + } + foreach($this->cookies as $name => $params){ + setcookie($name,$params['value'],$params['expire'],$params['path'],$params['domain'],$params['secure'],$params['httponly']); + } + header('Location: '.$url); + ob_flush(); + //nothing should be allowed to run after this command + exit; + } + +} \ No newline at end of file diff --git a/nano/core/widget/Widget.class.php b/nano/core/widget/Widget.class.php new file mode 100644 index 0000000..10589e0 --- /dev/null +++ b/nano/core/widget/Widget.class.php @@ -0,0 +1,177 @@ +pageInstance = $pageInstance; + $this->routing = \nano\core\routing\Routing::getInstance(); + $this->preLoad($this->routing,$this->pageInstance); + } + + /** + * Destructor. + * If the request was made via Ajax, the widget will be rendered on a call to the destructor + * if it has not already been. + */ + final public function __destruct() { + if($this->routing->isAjax()&&!$this->isRendered){ + $this->render(); + echo $this->renderedWidget; + $this->postOutput($this->routing,$this->pageInstance); + } + } + + /** + * Function - __set + * @param mixed $name Name + * @param $value Value + */ + final public function __set($name, $value) { + $this->data[$name] = $value; + } + + /** + * Function - &__get + * @param mixed $name Name + * @return mixed + */ + final public function &__get($name) { + return $this->data[$name]; + } + + /** + * Function - Render + */ + final public function render() { + if(!$this->isRendered){ + $this->isRendered = true; + $this->preExecute($this->routing,$this->pageInstance); + $this->templateToRender = $this->execute($this->routing,$this->pageInstance); + $this->postExecute($this->routing,$this->pageInstance); + $this->preRender($this->routing,$this->pageInstance); + $this->renderedWidget = \nano\core\view\View::load($this->templateToRender,$this->data,true); + $this->postRender($this->routing,$this->pageInstance); + } + } + + /** + * Render the widget (if it has not already been rendered, and return. + * @return string Rendered widget. + */ + final public function getRenderedWidget() { + $this->render(); + return $this->renderedWidget; + } + + /** + * Set the rendered output for the widget and mark it as having been rendered. + * @param string $data Rendered output. + */ + final public function setRenderedWidget($data) { + $this->isRendered = true; + $this->renderedWidget = $data; + } + + /** + * Set the filename of the template to use to render this widget. + * @param string $name Filename of template. + */ + final public function setTemplateToRender($name){ + $this->templateToRender = (string)$name; + } + + /** + * Get the filename of the template set to render this widget. + * @return string Filename of template. + */ + final public function getTemplateToRender(){ + return $this->templateToRender; + } + + /** + * Abstract method called by the widget constructor. + * @param \nano\core\routing\Routing $routing Shared routing instance. + * @param \nano\core\page\Page $pageInstance Page on which this widget is to be used. + */ + protected function preLoad(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + + } + + /** + * Abstract method called immediately on the first call to render(). + * @param \nano\core\routing\Routing $routing Shared routing instance. + * @param \nano\core\page\Page $pageInstance Page on which this widget is to be used. + */ + protected function preExecute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + + } + + /** + * Abstract method called by render() following preExecute(). + * When overridden, this should return the full path of the Twig template to render. + * @param \nano\core\routing\Routing $routing Shared routing instance. + * @param \nano\core\page\Page $pageInstance Page on which this widget is to be used. + */ + protected function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + + } + + /** + * Abstract method called by render() following execute(). + * @param \nano\core\routing\Routing $routing Shared routing instance. + * @param \nano\core\page\Page $pageInstance Page on which this widget is to be used. + */ + protected function postExecute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + + } + + /** + * Abstract method called by render() prior to rendering the Twig template. + * @param \nano\core\routing\Routing $routing Shared routing instance. + * @param \nano\core\page\Page $pageInstance Page on which this widget is to be used. + */ + protected function preRender(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + + } + + /** + * Abstract method called by render() after the Twig template has been rendered. + * @param \nano\core\routing\Routing $routing Shared routing instance. + * @param \nano\core\page\Page $pageInstance Page on which this widget is to be used. + */ + protected function postRender(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + + } + + /** + * Abstract method called after the widget has been output by the destructor. + * @param \nano\core\routing\Routing $routing Shared routing instance. + * @param \nano\core\page\Page $pageInstance Page on which this widget is to be used. + */ + protected function postOutput(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + + } + +} \ No newline at end of file diff --git a/plugins/SFTP/lib/SFTP.php b/plugins/SFTP/lib/SFTP.php new file mode 100644 index 0000000..f8500fc --- /dev/null +++ b/plugins/SFTP/lib/SFTP.php @@ -0,0 +1,113 @@ +connection = @ssh2_connect($host, $port); + if(!$this->connection) { + throw new Exception("Could not connect to $host on port $port."); + } + } + + /** + * Log in to the SFTP server. + * Throws an Exception if authentication fails, or if the SFTP subsystem could not be initialised. + * + * @param string $username Username for server. + * @param string $password Password for server. + */ + public function login($username, $password) { + if(!@ssh2_auth_password($this->connection, $username, $password)){ + throw new Exception("Could not authenticate with username $username " . "and password $password."); + } + $this->sftp = @ssh2_sftp($this->connection); + if(!$this->sftp){ + throw new Exception("Could not initialize SFTP subsystem."); + } + } + + /** + * Upload data to the specified file. + * If the file specified already exists on the server, it will be overwritten; if it does + * not exist, it will be created. + * An Exception will be thrown on any error. + * + * @param string $data_to_send Data. + * @param string $remote_file Full path to the remote file to write to. + */ + public function uploadData($data_to_send, $remote_file) { + $sftp = $this->sftp; + $stream = @fopen("ssh2.sftp://$sftp$remote_file", 'w'); + if(!$stream){ + throw new Exception("Could not open file: $remote_file"); + } + if(@fwrite($stream, $data_to_send) === false){ + throw new Exception("Could not send data from file: $local_file."); + } + @fclose($stream); + } + + /** + * Upload a file to the remote server. + * If the file specified already exists on the server, it will be overwritten; if it does + * not exist, it will be created. + * An Exception will be thrown on any error. + * + * @param string $local_file Path to the local file to send. + * @param string $remote_file Full path to the remote file to write to. + */ + public function uploadFile($local_file, $remote_file) { + $sftp = $this->sftp; + $stream = @fopen("ssh2.sftp://$sftp$remote_file", 'w'); + if(!$stream){ + throw new Exception("Could not open file: $remote_file"); + } + $data_to_send = @file_get_contents($local_file); + if($data_to_send === false){ + throw new Exception("Could not open local file: $local_file."); + } + if(@fwrite($stream, $data_to_send) === false){ + throw new Exception("Could not send data from file: $local_file."); + } + @fclose($stream); + } + + /** + * Get a file from the remote server. + * If the local file exists, it will be overwritten; if not, it will be created. + * + * @param string $remote_file Path to the remote file to be downloaded. + * @param string $local_file Path to the local file to write to. + */ + public function receiveFile($remote_file, $local_file) { + $sftp = $this->sftp; + $stream = @fopen("ssh2.sftp://$sftp$remote_file", 'r'); + if(!$stream){ + throw new Exception("Could not open file: $remote_file"); + } + $contents = fread($stream, filesize("ssh2.sftp://$sftp$remote_file")); + file_put_contents($local_file, $contents); + @fclose($stream); + } + + /** + * Delete a file from the remote server. + * @param string $remote_file Full path to the remote file to be removed. + */ + public function deleteFile($remote_file) { + $sftp = $this->sftp; + unlink("ssh2.sftp://$sftp$remote_file"); + } +} \ No newline at end of file diff --git a/plugins/SSH/lib/SSH.php b/plugins/SSH/lib/SSH.php new file mode 100644 index 0000000..1449dd1 --- /dev/null +++ b/plugins/SSH/lib/SSH.php @@ -0,0 +1,65 @@ +host = $host; + if($port!=null) { + $this->port = $port; + } + $this->con = @ssh2_connect($this->host, $this->port); + if(!$this->con) { + throw new Exception('Failed to connect to host'); + } + } + + public function auth($user,$password=null) { + $this->user = $user; + if($password!=null) { + $this->password = $password; + } + if(!ssh2_auth_password($this->con,$this->user,$this->password)) { + throw new Exception('Failed to connect to host'); + } + } + + public function openShell($shell_type=null) { + if($shell_type!=null) { + $this->shell_type = $shell_type; + } + $this->shell = @ssh2_shell($this->con,$this->shell_type); + if(!$this->shell){ + throw new Exception('Shell connection failed'); + } + } + + public function writeShell($command) { + fwrite($this->shell,$command."\n"); + } + + public function cmdExec() { + $argc = func_num_args(); + $argv = func_get_args(); + $cmd = ''; + for($i=0;$i<$argc;$i++) { + if($i!=($argc-1)) { + $cmd .= $argv[$i]." && "; + } else { + $cmd .= $argv[$i]; + } + } + echo $cmd; + $stream = ssh2_exec($this->con,$cmd); + stream_set_blocking($stream,true); + return fread( $stream, 4096 ); + } +} \ No newline at end of file diff --git a/plugins/SimpleImage/lib/SimpleImage.php b/plugins/SimpleImage/lib/SimpleImage.php new file mode 100644 index 0000000..50ed1e7 --- /dev/null +++ b/plugins/SimpleImage/lib/SimpleImage.php @@ -0,0 +1,85 @@ +image_type = $image_info[2]; + if( $this->image_type == IMAGETYPE_JPEG ) { + $this->image = imagecreatefromjpeg($filename); + } elseif( $this->image_type == IMAGETYPE_GIF ) { + $this->image = imagecreatefromgif($filename); + } elseif( $this->image_type == IMAGETYPE_PNG ) { + $this->image = imagecreatefrompng($filename); + } + } + function save($filename, $image_type=IMAGETYPE_JPEG, $compression=75, $permissions=null) { + if( $image_type == IMAGETYPE_JPEG ) { + imagejpeg($this->image,$filename,$compression); + } elseif( $image_type == IMAGETYPE_GIF ) { + imagegif($this->image,$filename); + } elseif( $image_type == IMAGETYPE_PNG ) { + imagepng($this->image,$filename); + } + if( $permissions != null) { + chmod($filename,$permissions); + } + } + function output($image_type=IMAGETYPE_JPEG) { + if( $image_type == IMAGETYPE_JPEG ) { + imagejpeg($this->image); + } elseif( $image_type == IMAGETYPE_GIF ) { + imagegif($this->image); + } elseif( $image_type == IMAGETYPE_PNG ) { + imagepng($this->image); + } + } + function getWidth() { + return imagesx($this->image); + } + function getHeight() { + return imagesy($this->image); + } + function resizeToHeight($height) { + $ratio = $height / $this->getHeight(); + $width = $this->getWidth() * $ratio; + $this->resize($width,$height); + } + function resizeToWidth($width) { + $ratio = $width / $this->getWidth(); + $height = $this->getheight() * $ratio; + $this->resize($width,$height); + } + function scale($scale) { + $width = $this->getWidth() * $scale/100; + $height = $this->getheight() * $scale/100; + $this->resize($width,$height); + } + function resize($width,$height) { + $new_image = imagecreatetruecolor($width, $height); + imagecopyresampled($new_image, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight()); + $this->image = $new_image; + } +} \ No newline at end of file diff --git a/plugins/sfYaml/LICENSE b/plugins/sfYaml/LICENSE new file mode 100755 index 0000000..3cef853 --- /dev/null +++ b/plugins/sfYaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2008-2009 Fabien Potencier + +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. diff --git a/plugins/sfYaml/README.markdown b/plugins/sfYaml/README.markdown new file mode 100755 index 0000000..e4f80cf --- /dev/null +++ b/plugins/sfYaml/README.markdown @@ -0,0 +1,15 @@ +Symfony YAML: A PHP library that speaks YAML +============================================ + +Symfony YAML is a PHP library that parses YAML strings and converts them to +PHP arrays. It can also converts PHP arrays to YAML strings. Its official +website is at http://components.symfony-project.org/yaml/. + +The documentation is to be found in the `doc/` directory. + +Symfony YAML is licensed under the MIT license (see LICENSE file). + +The Symfony YAML library is developed and maintained by the +[symfony](http://www.symfony-project.org/) project team. It has been extracted +from symfony to be used as a standalone library. Symfony YAML is part of the +[symfony components project](http://components.symfony-project.org/). diff --git a/plugins/sfYaml/doc/00-Introduction.markdown b/plugins/sfYaml/doc/00-Introduction.markdown new file mode 100755 index 0000000..e592758 --- /dev/null +++ b/plugins/sfYaml/doc/00-Introduction.markdown @@ -0,0 +1,143 @@ +Introduction +============ + +This book is about *Symfony YAML*, a PHP library part of the Symfony +Components project. Its official website is at +http://components.symfony-project.org/yaml/. + +>**SIDEBAR** +>About the Symfony Components +> +>[Symfony Components](http://components.symfony-project.org/) are +>standalone PHP classes that can be easily used in any +>PHP project. Most of the time, they have been developed as part of the +>[Symfony framework](http://www.symfony-project.org/), and decoupled from the +>main framework later on. You don't need to use the Symfony MVC framework to use +>the components. + +What is it? +----------- + +Symfony YAML is a PHP library that parses YAML strings and converts them to +PHP arrays. It can also converts PHP arrays to YAML strings. + +[YAML](http://www.yaml.org/), YAML Ain't Markup Language, is a human friendly +data serialization standard for all programming languages. YAML is a great +format for your configuration files. YAML files are as expressive as XML files +and as readable as INI files. + +### Easy to use + +There is only one archive to download, and you are ready to go. No +configuration, No installation. Drop the files in a directory and start using +it today in your projects. + +### Open-Source + +Released under the MIT license, you are free to do whatever you want, even in +a commercial environment. You are also encouraged to contribute. + + +### Used by popular Projects + +Symfony YAML was initially released as part of the symfony framework, one of +the most popular PHP web framework. It is also embedded in other popular +projects like PHPUnit or Doctrine. + +### Documented + +Symfony YAML is fully documented, with a dedicated online book, and of course +a full API documentation. + +### Fast + +One of the goal of Symfony YAML is to find the right balance between speed and +features. It supports just the needed feature to handle configuration files. + +### Unit tested + +The library is fully unit-tested. With more than 400 unit tests, the library +is stable and is already used in large projects. + +### Real Parser + +It sports a real parser and is able to parse a large subset of the YAML +specification, for all your configuration needs. It also means that the parser +is pretty robust, easy to understand, and simple enough to extend. + +### Clear error messages + +Whenever you have a syntax problem with your YAML files, the library outputs a +helpful message with the filename and the line number where the problem +occurred. It eases the debugging a lot. + +### Dump support + +It is also able to dump PHP arrays to YAML with object support, and inline +level configuration for pretty outputs. + +### Types Support + +It supports most of the YAML built-in types like dates, integers, octals, +booleans, and much more... + + +### Full merge key support + +Full support for references, aliases, and full merge key. Don't repeat +yourself by referencing common configuration bits. + +### PHP Embedding + +YAML files are dynamic. By embedding PHP code inside a YAML file, you have +even more power for your configuration files. + +Installation +------------ + +Symfony YAML can be installed by downloading the source code as a +[tar](http://github.com/fabpot/yaml/tarball/master) archive or a +[zip](http://github.com/fabpot/yaml/zipball/master) one. + +To stay up-to-date, you can also use the official Subversion +[repository](http://svn.symfony-project.com/components/yaml/). + +If you are a Git user, there is an official +[mirror](http://github.com/fabpot/yaml), which is updated every 10 minutes. + +If you prefer to install the component globally on your machine, you can use +the symfony [PEAR](http://pear.symfony-project.com/) channel server. + +Support +------- + +Support questions and enhancements can be discussed on the +[mailing-list](http://groups.google.com/group/symfony-components). + +If you find a bug, you can create a ticket at the symfony +[trac](http://trac.symfony-project.org/newticket) under the *YAML* component. + +License +------- + +The Symfony YAML component is licensed under the *MIT license*: + +>Copyright (c) 2008-2009 Fabien Potencier +> +>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. diff --git a/plugins/sfYaml/doc/01-Usage.markdown b/plugins/sfYaml/doc/01-Usage.markdown new file mode 100755 index 0000000..644cf11 --- /dev/null +++ b/plugins/sfYaml/doc/01-Usage.markdown @@ -0,0 +1,110 @@ +Using Symfony YAML +================== + +The Symfony YAML library is very simple and consists of two main classes: one +to parse YAML strings (`sfYamlParser`), and the other to dump a PHP array to +a YAML string (`sfYamlDumper`). + +On top of these two core classes, the main `sfYaml` class acts as a thin +wrapper and simplifies common uses. + +Reading YAML Files +------------------ + +The `sfYamlParser::parse()` method parses a YAML string and converts it to a +PHP array: + + [php] + $yaml = new sfYamlParser(); + $value = $yaml->parse(file_get_contents('/path/to/file.yaml')); + +If an error occurs during parsing, the parser throws an exception indicating +the error type and the line in the original YAML string where the error +occurred: + + [php] + try + { + $value = $yaml->parse(file_get_contents('/path/to/file.yaml')); + } + catch (InvalidArgumentException $e) + { + // an error occurred during parsing + echo "Unable to parse the YAML string: ".$e->getMessage(); + } + +>**TIP** +>As the parser is reentrant, you can use the same parser object to load +>different YAML strings. + +When loading a YAML file, it is sometimes better to use the `sfYaml::load()` +wrapper method: + + [php] + $loader = sfYaml::load('/path/to/file.yml'); + +The `sfYaml::load()` static method takes a YAML string or a file containing +YAML. Internally, it calls the `sfYamlParser::parse()` method, but with some +added bonuses: + + * It executes the YAML file as if it was a PHP file, so that you can embed + PHP commands in YAML files; + + * When a file cannot be parsed, it automatically adds the file name to the + error message, simplifying debugging when your application is loading + several YAML files. + +Writing YAML Files +------------------ + +The `sfYamlDumper` dumps any PHP array to its YAML representation: + + [php] + $array = array('foo' => 'bar', 'bar' => array('foo' => 'bar', 'bar' => 'baz')); + + $dumper = new sfYamlDumper(); + $yaml = $dumper->dump($array); + file_put_contents('/path/to/file.yaml', $yaml); + +>**NOTE** +>Of course, the Symfony YAML dumper is not able to dump resources. Also, +>even if the dumper is able to dump PHP objects, it is to be considered +>an alpha feature. + +If you only need to dump one array, you can use the `sfYaml::dump()` static +method shortcut: + + [php] + $yaml = sfYaml::dump($array, $inline); + +The YAML format supports two kind of representation for arrays, the expanded +one, and the inline one. By default, the dumper uses the inline +representation: + + [yml] + { foo: bar, bar: { foo: bar, bar: baz } } + +The second argument of the `dump()` method customizes the level at which the +output switches from the expanded representation to the inline one: + + [php] + echo $dumper->dump($array, 1); + +- + + [yml] + foo: bar + bar: { foo: bar, bar: baz } + +- + + [php] + echo $dumper->dump($array, 2); + +- + + [yml] + foo: bar + bar: + foo: bar + bar: baz diff --git a/plugins/sfYaml/doc/02-YAML.markdown b/plugins/sfYaml/doc/02-YAML.markdown new file mode 100755 index 0000000..a64c19e --- /dev/null +++ b/plugins/sfYaml/doc/02-YAML.markdown @@ -0,0 +1,312 @@ +The YAML Format +=============== + +According to the official [YAML](http://yaml.org/) website, YAML is "a human +friendly data serialization standard for all programming languages". + +Even if the YAML format can describe complex nested data structure, this +chapter only describes the minimum set of features needed to use YAML as a +configuration file format. + +YAML is a simple language that describes data. As PHP, it has a syntax for +simple types like strings, booleans, floats, or integers. But unlike PHP, it +makes a difference between arrays (sequences) and hashes (mappings). + +Scalars +------- + +The syntax for scalars is similar to the PHP syntax. + +### Strings + + [yml] + A string in YAML + +- + + [yml] + 'A singled-quoted string in YAML' + +>**TIP** +>In a single quoted string, a single quote `'` must be doubled: +> +> [yml] +> 'A single quote '' in a single-quoted string' + + [yml] + "A double-quoted string in YAML\n" + +Quoted styles are useful when a string starts or ends with one or more +relevant spaces. + +>**TIP** +>The double-quoted style provides a way to express arbitrary strings, by +>using `\` escape sequences. It is very useful when you need to embed a +>`\n` or a unicode character in a string. + +When a string contains line breaks, you can use the literal style, indicated +by the pipe (`|`), to indicate that the string will span several lines. In +literals, newlines are preserved: + + [yml] + | + \/ /| |\/| | + / / | | | |__ + +Alternatively, strings can be written with the folded style, denoted by `>`, +where each line break is replaced by a space: + + [yml] + > + This is a very long sentence + that spans several lines in the YAML + but which will be rendered as a string + without carriage returns. + +>**NOTE** +>Notice the two spaces before each line in the previous examples. They +>won't appear in the resulting PHP strings. + +### Numbers + + [yml] + # an integer + 12 + +- + + [yml] + # an octal + 014 + +- + + [yml] + # an hexadecimal + 0xC + +- + + [yml] + # a float + 13.4 + +- + + [yml] + # an exponential number + 1.2e+34 + +- + + [yml] + # infinity + .inf + +### Nulls + +Nulls in YAML can be expressed with `null` or `~`. + +### Booleans + +Booleans in YAML are expressed with `true` and `false`. + +>**NOTE** +>The symfony YAML parser also recognize `on`, `off`, `yes`, and `no` but +>it is strongly discouraged to use them as it has been removed from the +>1.2 YAML specifications. + +### Dates + +YAML uses the ISO-8601 standard to express dates: + + [yml] + 2001-12-14t21:59:43.10-05:00 + +- + + [yml] + # simple date + 2002-12-14 + +Collections +----------- + +A YAML file is rarely used to describe a simple scalar. Most of the time, it +describes a collection. A collection can be a sequence or a mapping of +elements. Both sequences and mappings are converted to PHP arrays. + +Sequences use a dash followed by a space (`- `): + + [yml] + - PHP + - Perl + - Python + +The previous YAML file is equivalent to the following PHP code: + + [php] + array('PHP', 'Perl', 'Python'); + +Mappings use a colon followed by a space (`: `) to mark each key/value pair: + + [yml] + PHP: 5.2 + MySQL: 5.1 + Apache: 2.2.20 + +which is equivalent to this PHP code: + + [php] + array('PHP' => 5.2, 'MySQL' => 5.1, 'Apache' => '2.2.20'); + +>**NOTE** +>In a mapping, a key can be any valid scalar. + +The number of spaces between the colon and the value does not matter: + + [yml] + PHP: 5.2 + MySQL: 5.1 + Apache: 2.2.20 + +YAML uses indentation with one or more spaces to describe nested collections: + + [yml] + "symfony 1.0": + PHP: 5.0 + Propel: 1.2 + "symfony 1.2": + PHP: 5.2 + Propel: 1.3 + +The following YAML is equivalent to the following PHP code: + + [php] + array( + 'symfony 1.0' => array( + 'PHP' => 5.0, + 'Propel' => 1.2, + ), + 'symfony 1.2' => array( + 'PHP' => 5.2, + 'Propel' => 1.3, + ), + ); + +There is one important thing you need to remember when using indentation in a +YAML file: *Indentation must be done with one or more spaces, but never with +tabulations*. + +You can nest sequences and mappings as you like: + + [yml] + 'Chapter 1': + - Introduction + - Event Types + 'Chapter 2': + - Introduction + - Helpers + +YAML can also use flow styles for collections, using explicit indicators +rather than indentation to denote scope. + +A sequence can be written as a comma separated list within square brackets +(`[]`): + + [yml] + [PHP, Perl, Python] + +A mapping can be written as a comma separated list of key/values within curly +braces (`{}`): + + [yml] + { PHP: 5.2, MySQL: 5.1, Apache: 2.2.20 } + +You can mix and match styles to achieve a better readability: + + [yml] + 'Chapter 1': [Introduction, Event Types] + 'Chapter 2': [Introduction, Helpers] + +- + + [yml] + "symfony 1.0": { PHP: 5.0, Propel: 1.2 } + "symfony 1.2": { PHP: 5.2, Propel: 1.3 } + +Comments +-------- + +Comments can be added in YAML by prefixing them with a hash mark (`#`): + + [yml] + # Comment on a line + "symfony 1.0": { PHP: 5.0, Propel: 1.2 } # Comment at the end of a line + "symfony 1.2": { PHP: 5.2, Propel: 1.3 } + +>**NOTE** +>Comments are simply ignored by the YAML parser and do not need to be +>indented according to the current level of nesting in a collection. + +Dynamic YAML files +------------------ + +In symfony, a YAML file can contain PHP code that is evaluated just before the +parsing occurs: + + [php] + 1.0: + version: + 1.1: + version: "" + +Be careful to not mess up with the indentation. Keep in mind the following +simple tips when adding PHP code to a YAML file: + + * The `` statements must always start the line or be embedded in a + value. + + * If a `` statement ends a line, you need to explicitly output a new + line ("\n"). + +
    + +A Full Length Example +--------------------- + +The following example illustrates most YAML notations explained in this +document: + + [yml] + "symfony 1.0": + end_of_maintainance: 2010-01-01 + is_stable: true + release_manager: "Grégoire Hubert" + description: > + This stable version is the right choice for projects + that need to be maintained for a long period of time. + latest_beta: ~ + latest_minor: 1.0.20 + supported_orms: [Propel] + archives: { source: [zip, tgz], sandbox: [zip, tgz] } + + "symfony 1.2": + end_of_maintainance: 2008-11-01 + is_stable: true + release_manager: 'Fabian Lange' + description: > + This stable version is the right choice + if you start a new project today. + latest_beta: null + latest_minor: 1.2.5 + supported_orms: + - Propel + - Doctrine + archives: + source: + - zip + - tgz + sandbox: + - zip + - tgz diff --git a/plugins/sfYaml/doc/A-License.markdown b/plugins/sfYaml/doc/A-License.markdown new file mode 100755 index 0000000..49cdd7c --- /dev/null +++ b/plugins/sfYaml/doc/A-License.markdown @@ -0,0 +1,108 @@ +Appendix A - License +==================== + +Attribution-Share Alike 3.0 Unported License +-------------------------------------------- + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. + +1. Definitions + + a. **"Adaptation"** means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. + + b. **"Collection"** means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. + + c. **"Creative Commons Compatible License"** means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. + + d. **"Distribute"** means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. + + e. **"License Elements"** means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. + + f. **"Licensor"** means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. + + g. **"Original Author"** means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. + + h. **"Work"** means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. + + i. **"You"** means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. + + j. **"Publicly Perform"** means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. + + k. **"Reproduce"** means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. + +2. Fair Dealing Rights + + Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. + +3. License Grant + + Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; + + b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; + + c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, + + d. to Distribute and Publicly Perform Adaptations. + + e. For the avoidance of doubt: + + i. **Non-waivable Compulsory License Schemes**. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; + + ii. **Waivable Compulsory License Schemes**. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, + + iii. **Voluntary License Schemes**. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. + + The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. + +4. Restrictions + + The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. + + b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. + + c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. + + d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. + +5. Representations, Warranties and Disclaimer + + UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability + + EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. + + b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. + + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. + + c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + + d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. + + e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. + + f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. + +>**SIDEBAR** +>Creative Commons Notice +> +>Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. +> +>Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. +> +>Creative Commons may be contacted at http://creativecommons.org/. diff --git a/plugins/sfYaml/lib/sfYaml.php b/plugins/sfYaml/lib/sfYaml.php new file mode 100755 index 0000000..1d89ccc --- /dev/null +++ b/plugins/sfYaml/lib/sfYaml.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * sfYaml offers convenience methods to load and dump YAML. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: sfYaml.class.php 8988 2008-05-15 20:24:26Z fabien $ + */ +class sfYaml +{ + static protected + $spec = '1.2'; + + /** + * Sets the YAML specification version to use. + * + * @param string $version The YAML specification version + */ + static public function setSpecVersion($version) + { + if (!in_array($version, array('1.1', '1.2'))) + { + throw new InvalidArgumentException(sprintf('Version %s of the YAML specifications is not supported', $version)); + } + + self::$spec = $version; + } + + /** + * Gets the YAML specification version to use. + * + * @return string The YAML specification version + */ + static public function getSpecVersion() + { + return self::$spec; + } + + /** + * Loads YAML into a PHP array. + * + * 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. + * + * Usage: + * + * $array = sfYaml::load('config.yml'); + * print_r($array); + * + * + * @param string $input Path of YAML file or string containing YAML + * + * @return array The YAML converted to a PHP array + * + * @throws InvalidArgumentException If the YAML is not valid + */ + public static function load($input) + { + $file = ''; + + // if input is a file, process it + if (strpos($input, "\n") === false && is_file($input)) + { + $file = $input; + + ob_start(); + $retval = include($input); + $content = ob_get_clean(); + + // if an array is returned by the config file assume it's in plain php form else in YAML + $input = is_array($retval) ? $retval : $content; + } + + // if an array is returned by the config file assume it's in plain php form else in YAML + if (is_array($input)) + { + return $input; + } + + require_once dirname(__FILE__).'/sfYamlParser.php'; + + $yaml = new sfYamlParser(); + + try + { + $ret = $yaml->parse($input); + } + catch (Exception $e) + { + throw new InvalidArgumentException(sprintf('Unable to parse %s: %s', $file ? sprintf('file "%s"', $file) : 'string', $e->getMessage())); + } + + return $ret; + } + + /** + * Dumps a PHP array to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param array $array PHP array + * @param integer $inline The level where you switch to inline YAML + * + * @return string A YAML string representing the original PHP array + */ + public static function dump($array, $inline = 2) + { + require_once dirname(__FILE__).'/sfYamlDumper.php'; + + $yaml = new sfYamlDumper(); + + return $yaml->dump($array, $inline); + } +} + +/** + * Wraps echo to automatically provide a newline. + * + * @param string $string The string to echo with new line + */ +function echoln($string) +{ + echo $string."\n"; +} diff --git a/plugins/sfYaml/lib/sfYamlDumper.php b/plugins/sfYaml/lib/sfYamlDumper.php new file mode 100755 index 0000000..0ada2b3 --- /dev/null +++ b/plugins/sfYaml/lib/sfYamlDumper.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once(dirname(__FILE__).'/sfYamlInline.php'); + +/** + * sfYamlDumper dumps PHP variables to YAML strings. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: sfYamlDumper.class.php 10575 2008-08-01 13:08:42Z nicolas $ + */ +class sfYamlDumper +{ + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param integer $inline The level where you switch to inline YAML + * @param integer $indent The level o indentation indentation (used internally) + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, $inline = 0, $indent = 0) + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + + if ($inline <= 0 || !is_array($input) || empty($input)) + { + $output .= $prefix.sfYamlInline::dump($input); + } + else + { + $isAHash = array_keys($input) !== range(0, count($input) - 1); + + foreach ($input as $key => $value) + { + $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $isAHash ? sfYamlInline::dump($key).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + 2) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/plugins/sfYaml/lib/sfYamlInline.php b/plugins/sfYaml/lib/sfYamlInline.php new file mode 100755 index 0000000..a88cbb3 --- /dev/null +++ b/plugins/sfYaml/lib/sfYamlInline.php @@ -0,0 +1,442 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once dirname(__FILE__).'/sfYaml.php'; + +/** + * sfYamlInline implements a YAML parser/dumper for the YAML inline syntax. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: sfYamlInline.class.php 16177 2009-03-11 08:32:48Z fabien $ + */ +class sfYamlInline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; + + /** + * Convert a YAML string to a PHP array. + * + * @param string $value A YAML string + * + * @return array A PHP array representing the YAML string + */ + static public function load($value) + { + $value = trim($value); + + if (0 == strlen($value)) + { + return ''; + } + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) + { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + switch ($value[0]) + { + case '[': + $result = self::parseSequence($value); + break; + case '{': + $result = self::parseMapping($value); + break; + default: + $result = self::parseScalar($value); + } + + if (isset($mbEncoding)) + { + mb_internal_encoding($mbEncoding); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * + * @return string The YAML string representing the PHP array + */ + static public function dump($value) + { + if ('1.1' === sfYaml::getSpecVersion()) + { + $trueValues = array('true', 'on', '+', 'yes', 'y'); + $falseValues = array('false', 'off', '-', 'no', 'n'); + } + else + { + $trueValues = array('true'); + $falseValues = array('false'); + } + + switch (true) + { + case is_resource($value): + throw new InvalidArgumentException('Unable to dump PHP resources in a YAML file.'); + case is_object($value): + return '!!php/object:'.serialize($value); + case is_array($value): + return self::dumpArray($value); + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case ctype_digit($value): + return is_string($value) ? "'$value'" : (int) $value; + case is_numeric($value): + return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value); + case false !== strpos($value, "\n") || false !== strpos($value, "\r"): + return sprintf('"%s"', str_replace(array('"', "\n", "\r"), array('\\"', '\n', '\r'), $value)); + case preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ - ? | < > = ! % @ ` ]/x', $value): + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + case '' == $value: + return "''"; + case preg_match(self::getTimestampRegex(), $value): + return "'$value'"; + case in_array(strtolower($value), $trueValues): + return "'$value'"; + case in_array(strtolower($value), $falseValues): + return "'$value'"; + case in_array(strtolower($value), array('null', '~')): + return "'$value'"; + default: + return $value; + } + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * + * @return string The YAML string representing the PHP array + */ + static protected function dumpArray($value) + { + // array + $keys = array_keys($value); + if ( + (1 == count($keys) && '0' == $keys[0]) + || + (count($keys) > 1 && array_reduce($keys, create_function('$v,$w', 'return (integer) $v + $w;'), 0) == count($keys) * (count($keys) - 1) / 2)) + { + $output = array(); + foreach ($value as $val) + { + $output[] = self::dump($val); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // mapping + $output = array(); + foreach ($value as $key => $val) + { + $output[] = sprintf('%s: %s', self::dump($key), self::dump($val)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + /** + * Parses a scalar to a YAML string. + * + * @param scalar $scalar + * @param string $delimiters + * @param array $stringDelimiter + * @param integer $i + * @param boolean $evaluate + * + * @return string A YAML string + */ + static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) + { + if (in_array($scalar[$i], $stringDelimiters)) + { + // quoted scalar + $output = self::parseQuotedScalar($scalar, $i); + } + else + { + // "normal" string + if (!$delimiters) + { + $output = substr($scalar, $i); + $i += strlen($output); + + // remove comments + if (false !== $strpos = strpos($output, ' #')) + { + $output = rtrim(substr($output, 0, $strpos)); + } + } + else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) + { + $output = $match[1]; + $i += strlen($output); + } + else + { + throw new InvalidArgumentException(sprintf('Malformed inline YAML string (%s).', $scalar)); + } + + $output = $evaluate ? self::evaluateScalar($output) : $output; + } + + return $output; + } + + /** + * Parses a quoted scalar to YAML. + * + * @param string $scalar + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseQuotedScalar($scalar, &$i) + { + if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) + { + throw new InvalidArgumentException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); + } + + $output = substr($match[0], 1, strlen($match[0]) - 2); + + if ('"' == $scalar[$i]) + { + // evaluate the string + $output = str_replace(array('\\"', '\\n', '\\r'), array('"', "\n", "\r"), $output); + } + else + { + // unescape ' + $output = str_replace('\'\'', '\'', $output); + } + + $i += strlen($match[0]); + + return $output; + } + + /** + * Parses a sequence to a YAML string. + * + * @param string $sequence + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseSequence($sequence, &$i = 0) + { + $output = array(); + $len = strlen($sequence); + $i += 1; + + // [foo, bar, ...] + while ($i < $len) + { + switch ($sequence[$i]) + { + case '[': + // nested sequence + $output[] = self::parseSequence($sequence, $i); + break; + case '{': + // nested mapping + $output[] = self::parseMapping($sequence, $i); + break; + case ']': + return $output; + case ',': + case ' ': + break; + default: + $isQuoted = in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); + + if (!$isQuoted && false !== strpos($value, ': ')) + { + // embedded mapping? + try + { + $value = self::parseMapping('{'.$value.'}'); + } + catch (InvalidArgumentException $e) + { + // no, it's not + } + } + + $output[] = $value; + + --$i; + } + + ++$i; + } + + throw new InvalidArgumentException(sprintf('Malformed inline YAML string %s', $sequence)); + } + + /** + * Parses a mapping to a YAML string. + * + * @param string $mapping + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseMapping($mapping, &$i = 0) + { + $output = array(); + $len = strlen($mapping); + $i += 1; + + // {foo: bar, bar:foo, ...} + while ($i < $len) + { + switch ($mapping[$i]) + { + case ' ': + case ',': + ++$i; + continue 2; + case '}': + return $output; + } + + // key + $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + + // value + $done = false; + while ($i < $len) + { + switch ($mapping[$i]) + { + case '[': + // nested sequence + $output[$key] = self::parseSequence($mapping, $i); + $done = true; + break; + case '{': + // nested mapping + $output[$key] = self::parseMapping($mapping, $i); + $done = true; + break; + case ':': + case ' ': + break; + default: + $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); + $done = true; + --$i; + } + + ++$i; + + if ($done) + { + continue 2; + } + } + } + + throw new InvalidArgumentException(sprintf('Malformed inline YAML string %s', $mapping)); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @param string $scalar + * + * @return string A YAML string + */ + static protected function evaluateScalar($scalar) + { + $scalar = trim($scalar); + + if ('1.1' === sfYaml::getSpecVersion()) + { + $trueValues = array('true', 'on', '+', 'yes', 'y'); + $falseValues = array('false', 'off', '-', 'no', 'n'); + } + else + { + $trueValues = array('true'); + $falseValues = array('false'); + } + + switch (true) + { + case 'null' == strtolower($scalar): + case '' == $scalar: + case '~' == $scalar: + return null; + case 0 === strpos($scalar, '!str'): + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '! '): + return intval(self::parseScalar(substr($scalar, 2))); + case 0 === strpos($scalar, '!!php/object:'): + return unserialize(substr($scalar, 13)); + case ctype_digit($scalar): + $raw = $scalar; + $cast = intval($scalar); + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case in_array(strtolower($scalar), $trueValues): + return true; + case in_array(strtolower($scalar), $falseValues): + return false; + case is_numeric($scalar): + return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); + case 0 == strcasecmp($scalar, '.inf'): + case 0 == strcasecmp($scalar, '.NaN'): + return -log(0); + case 0 == strcasecmp($scalar, '-.inf'): + return log(0); + case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return floatval(str_replace(',', '', $scalar)); + case preg_match(self::getTimestampRegex(), $scalar): + return strtotime($scalar); + default: + return (string) $scalar; + } + } + + static protected function getTimestampRegex() + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } +} diff --git a/plugins/sfYaml/lib/sfYamlParser.php b/plugins/sfYaml/lib/sfYamlParser.php new file mode 100755 index 0000000..91da2dc --- /dev/null +++ b/plugins/sfYaml/lib/sfYamlParser.php @@ -0,0 +1,622 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once(dirname(__FILE__).'/sfYamlInline.php'); + +if (!defined('PREG_BAD_UTF8_OFFSET_ERROR')) +{ + define('PREG_BAD_UTF8_OFFSET_ERROR', 5); +} + +/** + * sfYamlParser parses YAML strings to convert them to PHP arrays. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: sfYamlParser.class.php 10832 2008-08-13 07:46:08Z fabien $ + */ +class sfYamlParser +{ + protected + $offset = 0, + $lines = array(), + $currentLineNb = -1, + $currentLine = '', + $refs = array(); + + /** + * Constructor + * + * @param integer $offset The offset of YAML document (used for line numbers in error messages) + */ + public function __construct($offset = 0) + { + $this->offset = $offset; + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * + * @return mixed A PHP value + * + * @throws InvalidArgumentException If the YAML is not valid + */ + public function parse($value) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $this->lines = explode("\n", $this->cleanup($value)); + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) + { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + + $data = array(); + while ($this->moveToNextLine()) + { + if ($this->isCurrentLineEmpty()) + { + continue; + } + + // tab? + if (preg_match('#^\t+#', $this->currentLine)) + { + throw new InvalidArgumentException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + + $isRef = $isInPlace = $isProcessed = false; + if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#u', $this->currentLine, $values)) + { + if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) + { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) + { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new sfYamlParser($c); + $parser->refs =& $this->refs; + $data[] = $parser->parse($this->getNextEmbedBlock()); + } + else + { + if (isset($values['leadspaces']) + && ' ' == $values['leadspaces'] + && preg_match('#^(?P'.sfYamlInline::REGEX_QUOTED_STRING.'|[^ \'"\{].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches)) + { + // this is a compact notation element, add to next block and parse + $c = $this->getRealCurrentLineNb(); + $parser = new sfYamlParser($c); + $parser->refs =& $this->refs; + + $block = $values['value']; + if (!$this->isNextLineIndented()) + { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2); + } + + $data[] = $parser->parse($block); + } + else + { + $data[] = $this->parseValue($values['value']); + } + } + } + else if (preg_match('#^(?P'.sfYamlInline::REGEX_QUOTED_STRING.'|[^ \'"].*?) *\:(\s+(?P.+?))?\s*$#u', $this->currentLine, $values)) + { + $key = sfYamlInline::parseScalar($values['key']); + + if ('<<' === $key) + { + if (isset($values['value']) && '*' === substr($values['value'], 0, 1)) + { + $isInPlace = substr($values['value'], 1); + if (!array_key_exists($isInPlace, $this->refs)) + { + throw new InvalidArgumentException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + else + { + if (isset($values['value']) && $values['value'] !== '') + { + $value = $values['value']; + } + else + { + $value = $this->getNextEmbedBlock(); + } + $c = $this->getRealCurrentLineNb() + 1; + $parser = new sfYamlParser($c); + $parser->refs =& $this->refs; + $parsed = $parser->parse($value); + + $merged = array(); + if (!is_array($parsed)) + { + throw new InvalidArgumentException(sprintf("YAML merge keys used with a scalar value instead of an array at line %s (%s)", $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + else if (isset($parsed[0])) + { + // Numeric array, merge individual elements + foreach (array_reverse($parsed) as $parsedItem) + { + if (!is_array($parsedItem)) + { + throw new InvalidArgumentException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() + 1, $parsedItem)); + } + $merged = array_merge($parsedItem, $merged); + } + } + else + { + // Associative array, merge + $merged = array_merge($merge, $parsed); + } + + $isProcessed = $merged; + } + } + else if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) + { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + if ($isProcessed) + { + // Merge keys + $data = $isProcessed; + } + // hash + else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) + { + // if next line is less indented or equal, then it means that the current value is null + if ($this->isNextLineIndented()) + { + $data[$key] = null; + } + else + { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new sfYamlParser($c); + $parser->refs =& $this->refs; + $data[$key] = $parser->parse($this->getNextEmbedBlock()); + } + } + else + { + if ($isInPlace) + { + $data = $this->refs[$isInPlace]; + } + else + { + $data[$key] = $this->parseValue($values['value']); + } + } + } + else + { + // 1-liner followed by newline + if (2 == count($this->lines) && empty($this->lines[1])) + { + $value = sfYamlInline::load($this->lines[0]); + if (is_array($value)) + { + $first = reset($value); + if ('*' === substr($first, 0, 1)) + { + $data = array(); + foreach ($value as $alias) + { + $data[] = $this->refs[substr($alias, 1)]; + } + $value = $data; + } + } + + if (isset($mbEncoding)) + { + mb_internal_encoding($mbEncoding); + } + + return $value; + } + + switch (preg_last_error()) + { + case PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error on line'; + break; + case PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached on line'; + break; + case PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached on line'; + break; + case PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data on line'; + break; + case PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line'; + break; + default: + $error = 'Unable to parse line'; + } + + throw new InvalidArgumentException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + + if ($isRef) + { + $this->refs[$isRef] = end($data); + } + } + + if (isset($mbEncoding)) + { + mb_internal_encoding($mbEncoding); + } + + return empty($data) ? null : $data; + } + + /** + * Returns the current line number (takes the offset into account). + * + * @return integer The current line number + */ + protected function getRealCurrentLineNb() + { + return $this->currentLineNb + $this->offset; + } + + /** + * Returns the current line indentation. + * + * @return integer The current line indentation + */ + protected function getCurrentLineIndentation() + { + return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param integer $indentation The indent level at which the block is to be read, or null for default + * + * @return string A YAML string + */ + protected function getNextEmbedBlock($indentation = null) + { + $this->moveToNextLine(); + + if (null === $indentation) + { + $newIndent = $this->getCurrentLineIndentation(); + + if (!$this->isCurrentLineEmpty() && 0 == $newIndent) + { + throw new InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + else + { + $newIndent = $indentation; + } + + $data = array(substr($this->currentLine, $newIndent)); + + while ($this->moveToNextLine()) + { + if ($this->isCurrentLineEmpty()) + { + if ($this->isCurrentLineBlank()) + { + $data[] = substr($this->currentLine, $newIndent); + } + + continue; + } + + $indent = $this->getCurrentLineIndentation(); + + if (preg_match('#^(?P *)$#', $this->currentLine, $match)) + { + // empty line + $data[] = $match['text']; + } + else if ($indent >= $newIndent) + { + $data[] = substr($this->currentLine, $newIndent); + } + else if (0 == $indent) + { + $this->moveToPreviousLine(); + + break; + } + else + { + throw new InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + + return implode("\n", $data); + } + + /** + * Moves the parser to the next line. + */ + protected function moveToNextLine() + { + if ($this->currentLineNb >= count($this->lines) - 1) + { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + protected function moveToPreviousLine() + { + $this->currentLine = $this->lines[--$this->currentLineNb]; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * + * @return mixed A PHP value + */ + protected function parseValue($value) + { + if ('*' === substr($value, 0, 1)) + { + if (false !== $pos = strpos($value, '#')) + { + $value = substr($value, 1, $pos - 2); + } + else + { + $value = substr($value, 1); + } + + if (!array_key_exists($value, $this->refs)) + { + throw new InvalidArgumentException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine)); + } + return $this->refs[$value]; + } + + if (preg_match('/^(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?$/', $value, $matches)) + { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers))); + } + else + { + return sfYamlInline::load($value); + } + } + + /** + * Parses a folded scalar. + * + * @param string $separator The separator that was used to begin this folded scalar (| or >) + * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) + * @param integer $indentation The indentation that was used to begin this folded scalar + * + * @return string The text value + */ + protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0) + { + $separator = '|' == $separator ? "\n" : ' '; + $text = ''; + + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineBlank()) + { + $text .= "\n"; + + $notEOF = $this->moveToNextLine(); + } + + if (!$notEOF) + { + return ''; + } + + if (!preg_match('#^(?P'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P.*)$#u', $this->currentLine, $matches)) + { + $this->moveToPreviousLine(); + + return ''; + } + + $textIndent = $matches['indent']; + $previousIndent = 0; + + $text .= $matches['text'].$separator; + while ($this->currentLineNb + 1 < count($this->lines)) + { + $this->moveToNextLine(); + + if (preg_match('#^(?P {'.strlen($textIndent).',})(?P.+)$#u', $this->currentLine, $matches)) + { + if (' ' == $separator && $previousIndent != $matches['indent']) + { + $text = substr($text, 0, -1)."\n"; + } + $previousIndent = $matches['indent']; + + $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator); + } + else if (preg_match('#^(?P *)$#', $this->currentLine, $matches)) + { + $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n"; + } + else + { + $this->moveToPreviousLine(); + + break; + } + } + + if (' ' == $separator) + { + // replace last separator by a newline + $text = preg_replace('/ (\n*)$/', "\n$1", $text); + } + + switch ($indicator) + { + case '': + $text = preg_replace('#\n+$#s', "\n", $text); + break; + case '+': + break; + case '-': + $text = preg_replace('#\n+$#s', '', $text); + break; + } + + return $text; + } + + /** + * Returns true if the next line is indented. + * + * @return Boolean Returns true if the next line is indented, false otherwise + */ + protected function isNextLineIndented() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineEmpty()) + { + $notEOF = $this->moveToNextLine(); + } + + if (false === $notEOF) + { + return false; + } + + $ret = false; + if ($this->getCurrentLineIndentation() <= $currentIndentation) + { + $ret = true; + } + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise + */ + protected function isCurrentLineEmpty() + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return Boolean Returns true if the current line is blank, false otherwise + */ + protected function isCurrentLineBlank() + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return Boolean Returns true if the current line is a comment line, false otherwise + */ + protected function isCurrentLineComment() + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + return $ltrimmedLine[0] === '#'; + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + protected function cleanup($value) + { + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + if (!preg_match("#\n$#", $value)) + { + $value .= "\n"; + } + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#su', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if ($count == 1) + { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if ($count == 1) + { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#s', '', $value); + } + + return $value; + } +} diff --git a/plugins/sfYaml/package.xml b/plugins/sfYaml/package.xml new file mode 100755 index 0000000..1869ae9 --- /dev/null +++ b/plugins/sfYaml/package.xml @@ -0,0 +1,102 @@ + + + YAML + pear.symfony-project.com + The Symfony YAML Component. + The Symfony YAML Component. + + Fabien Potencier + fabpot + fabien.potencier@symfony-project.org + yes + + 2009-12-01 + + 1.0.2 + 1.0.0 + + + stable + stable + + MIT license + - + + + + + + + + + + + + + + + + + + 5.2.4 + + + 1.4.1 + + + + + + + + + + + 1.0.2 + 1.0.0 + + + stable + stable + + MIT license + 2009-12-01 + MIT + + * fabien: fixed \ usage in quoted string + + + + + 1.0.1 + 1.0.0 + + + stable + stable + + MIT license + 2009-12-01 + MIT + + * fabien: fixed a possible loop in parsing a non-valid quoted string + + + + + 1.0.0 + 1.0.0 + + + stable + stable + + MIT license + 2009-11-30 + MIT + + * fabien: first stable release as a Symfony Component + + + + diff --git a/plugins/sfYaml/test/fixtures/YtsAnchorAlias.yml b/plugins/sfYaml/test/fixtures/YtsAnchorAlias.yml new file mode 100755 index 0000000..5f9c942 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsAnchorAlias.yml @@ -0,0 +1,31 @@ +--- %YAML:1.0 +test: Simple Alias Example +brief: > + If you need to refer to the same item of data twice, + you can give that item an alias. The alias is a plain + string, starting with an ampersand. The item may then + be referred to by the alias throughout your document + by using an asterisk before the name of the alias. + This is called an anchor. +yaml: | + - &showell Steve + - Clark + - Brian + - Oren + - *showell +php: | + array('Steve', 'Clark', 'Brian', 'Oren', 'Steve') + +--- +test: Alias of a Mapping +brief: > + An alias can be used on any item of data, including + sequences, mappings, and other complex data types. +yaml: | + - &hello + Meat: pork + Starch: potato + - banana + - *hello +php: | + array(array('Meat'=>'pork', 'Starch'=>'potato'), 'banana', array('Meat'=>'pork', 'Starch'=>'potato')) diff --git a/plugins/sfYaml/test/fixtures/YtsBasicTests.yml b/plugins/sfYaml/test/fixtures/YtsBasicTests.yml new file mode 100755 index 0000000..eafee68 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsBasicTests.yml @@ -0,0 +1,178 @@ +--- %YAML:1.0 +test: Simple Sequence +brief: | + You can specify a list in YAML by placing each + member of the list on a new line with an opening + dash. These lists are called sequences. +yaml: | + - apple + - banana + - carrot +php: | + array('apple', 'banana', 'carrot') +--- +test: Nested Sequences +brief: | + You can include a sequence within another + sequence by giving the sequence an empty + dash, followed by an indented list. +yaml: | + - + - foo + - bar + - baz +php: | + array(array('foo', 'bar', 'baz')) +--- +test: Mixed Sequences +brief: | + Sequences can contain any YAML data, + including strings and other sequences. +yaml: | + - apple + - + - foo + - bar + - x123 + - banana + - carrot +php: | + array('apple', array('foo', 'bar', 'x123'), 'banana', 'carrot') +--- +test: Deeply Nested Sequences +brief: | + Sequences can be nested even deeper, with each + level of indentation representing a level of + depth. +yaml: | + - + - + - uno + - dos +php: | + array(array(array('uno', 'dos'))) +--- +test: Simple Mapping +brief: | + You can add a keyed list (also known as a dictionary or + hash) to your document by placing each member of the + list on a new line, with a colon seperating the key + from its value. In YAML, this type of list is called + a mapping. +yaml: | + foo: whatever + bar: stuff +php: | + array('foo' => 'whatever', 'bar' => 'stuff') +--- +test: Sequence in a Mapping +brief: | + A value in a mapping can be a sequence. +yaml: | + foo: whatever + bar: + - uno + - dos +php: | + array('foo' => 'whatever', 'bar' => array('uno', 'dos')) +--- +test: Nested Mappings +brief: | + A value in a mapping can be another mapping. +yaml: | + foo: whatever + bar: + fruit: apple + name: steve + sport: baseball +php: | + array( + 'foo' => 'whatever', + 'bar' => array( + 'fruit' => 'apple', + 'name' => 'steve', + 'sport' => 'baseball' + ) + ) +--- +test: Mixed Mapping +brief: | + A mapping can contain any assortment + of mappings and sequences as values. +yaml: | + foo: whatever + bar: + - + fruit: apple + name: steve + sport: baseball + - more + - + python: rocks + perl: papers + ruby: scissorses +php: | + array( + 'foo' => 'whatever', + 'bar' => array( + array( + 'fruit' => 'apple', + 'name' => 'steve', + 'sport' => 'baseball' + ), + 'more', + array( + 'python' => 'rocks', + 'perl' => 'papers', + 'ruby' => 'scissorses' + ) + ) + ) +--- +test: Mapping-in-Sequence Shortcut +todo: true +brief: | + If you are adding a mapping to a sequence, you + can place the mapping on the same line as the + dash as a shortcut. +yaml: | + - work on YAML.py: + - work on Store +php: | + array(array('work on YAML.py' => array('work on Store'))) +--- +test: Sequence-in-Mapping Shortcut +todo: true +brief: | + The dash in a sequence counts as indentation, so + you can add a sequence inside of a mapping without + needing spaces as indentation. +yaml: | + allow: + - 'localhost' + - '%.sourceforge.net' + - '%.freepan.org' +php: | + array('allow' => array('localhost', '%.sourceforge.net', '%.freepan.org')) +--- +todo: true +test: Merge key +brief: | + A merge key ('<<') can be used in a mapping to insert other mappings. If + the value associated with the merge key is a mapping, each of its key/value + pairs is inserted into the current mapping. +yaml: | + mapping: + name: Joe + job: Accountant + <<: + age: 38 +php: | + array( + 'mapping' => + array( + 'name' => 'Joe', + 'job' => 'Accountant', + 'age' => 38 + ) + ) diff --git a/plugins/sfYaml/test/fixtures/YtsBlockMapping.yml b/plugins/sfYaml/test/fixtures/YtsBlockMapping.yml new file mode 100755 index 0000000..3a2d410 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsBlockMapping.yml @@ -0,0 +1,52 @@ +--- +test: One Element Mapping +brief: | + A mapping with one key/value pair +yaml: | + foo: bar +php: | + array('foo' => 'bar') +--- +test: Multi Element Mapping +brief: | + More than one key/value pair +yaml: | + red: baron + white: walls + blue: berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) +--- +test: Values aligned +brief: | + Often times human editors of documents will align the values even + though YAML emitters generally don't. +yaml: | + red: baron + white: walls + blue: berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) +--- +test: Colons aligned +brief: | + Spaces can come before the ': ' key/value separator. +yaml: | + red : baron + white : walls + blue : berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) + \ No newline at end of file diff --git a/plugins/sfYaml/test/fixtures/YtsDocumentSeparator.yml b/plugins/sfYaml/test/fixtures/YtsDocumentSeparator.yml new file mode 100755 index 0000000..d0e3877 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsDocumentSeparator.yml @@ -0,0 +1,85 @@ +--- %YAML:1.0 +test: Trailing Document Separator +todo: true +brief: > + You can separate YAML documents + with a string of three dashes. +yaml: | + - foo: 1 + bar: 2 + --- + more: stuff +python: | + [ + [ { 'foo': 1, 'bar': 2 } ], + { 'more': 'stuff' } + ] +ruby: | + [ { 'foo' => 1, 'bar' => 2 } ] + +--- +test: Leading Document Separator +todo: true +brief: > + You can explicity give an opening + document separator to your YAML stream. +yaml: | + --- + - foo: 1 + bar: 2 + --- + more: stuff +python: | + [ + [ {'foo': 1, 'bar': 2}], + {'more': 'stuff'} + ] +ruby: | + [ { 'foo' => 1, 'bar' => 2 } ] + +--- +test: YAML Header +todo: true +brief: > + The opening separator can contain directives + to the YAML parser, such as the version + number. +yaml: | + --- %YAML:1.0 + foo: 1 + bar: 2 +php: | + array('foo' => 1, 'bar' => 2) +documents: 1 + +--- +test: Red Herring Document Separator +brief: > + Separators included in blocks or strings + are treated as blocks or strings, as the + document separator should have no indentation + preceding it. +yaml: | + foo: | + --- +php: | + array('foo' => "---\n") + +--- +test: Multiple Document Separators in Block +brief: > + This technique allows you to embed other YAML + documents within literal blocks. +yaml: | + foo: | + --- + foo: bar + --- + yo: baz + bar: | + fooness +php: | + array( + 'foo' => "---\nfoo: bar\n---\nyo: baz\n", + 'bar' => "fooness\n" + ) diff --git a/plugins/sfYaml/test/fixtures/YtsErrorTests.yml b/plugins/sfYaml/test/fixtures/YtsErrorTests.yml new file mode 100755 index 0000000..c0d6d6d --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsErrorTests.yml @@ -0,0 +1,26 @@ +--- +test: Missing value for hash item +todo: true +brief: | + Third item in this hash doesn't have a value +yaml: | + okay: value + also okay: ~ + causes error because no value specified + last key: value okay here too +python-error: causes error because no value specified + +--- +test: Not indenting enough +brief: | + There was a bug in PyYaml where it was off by one + in the indentation check. It was allowing the YAML + below. +# This is actually valid YAML now. Someone should tell showell. +yaml: | + foo: + firstline: 1 + secondline: 2 +php: | + array('foo' => null, 'firstline' => 1, 'secondline' => 2) + diff --git a/plugins/sfYaml/test/fixtures/YtsFlowCollections.yml b/plugins/sfYaml/test/fixtures/YtsFlowCollections.yml new file mode 100755 index 0000000..56bf6b8 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsFlowCollections.yml @@ -0,0 +1,60 @@ +--- +test: Simple Inline Array +brief: > + Sequences can be contained on a + single line, using the inline syntax. + Separate each entry with commas and + enclose in square brackets. +yaml: | + seq: [ a, b, c ] +php: | + array('seq' => array('a', 'b', 'c')) +--- +test: Simple Inline Hash +brief: > + Mapping can also be contained on + a single line, using the inline + syntax. Each key-value pair is + separated by a colon, with a comma + between each entry in the mapping. + Enclose with curly braces. +yaml: | + hash: { name: Steve, foo: bar } +php: | + array('hash' => array('name' => 'Steve', 'foo' => 'bar')) +--- +test: Multi-line Inline Collections +todo: true +brief: > + Both inline sequences and inline mappings + can span multiple lines, provided that you + indent the additional lines. +yaml: | + languages: [ Ruby, + Perl, + Python ] + websites: { YAML: yaml.org, + Ruby: ruby-lang.org, + Python: python.org, + Perl: use.perl.org } +php: | + array( + 'languages' => array('Ruby', 'Perl', 'Python'), + 'websites' => array( + 'YAML' => 'yaml.org', + 'Ruby' => 'ruby-lang.org', + 'Python' => 'python.org', + 'Perl' => 'use.perl.org' + ) + ) +--- +test: Commas in Values (not in the spec!) +todo: true +brief: > + List items in collections are delimited by commas, but + there must be a space after each comma. This allows you + to add numbers without quoting. +yaml: | + attendances: [ 45,123, 70,000, 17,222 ] +php: | + array('attendances' => array(45123, 70000, 17222)) diff --git a/plugins/sfYaml/test/fixtures/YtsFoldedScalars.yml b/plugins/sfYaml/test/fixtures/YtsFoldedScalars.yml new file mode 100755 index 0000000..d1432f6 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsFoldedScalars.yml @@ -0,0 +1,176 @@ +--- %YAML:1.0 +test: Single ending newline +brief: > + A pipe character, followed by an indented + block of text is treated as a literal + block, in which newlines are preserved + throughout the block, including the final + newline. +yaml: | + --- + this: | + Foo + Bar +php: | + array('this' => "Foo\nBar\n") +--- +test: The '+' indicator +brief: > + The '+' indicator says to keep newlines at the end of text + blocks. +yaml: | + normal: | + extra new lines not kept + + preserving: |+ + extra new lines are kept + + + dummy: value +php: | + array( + 'normal' => "extra new lines not kept\n", + 'preserving' => "extra new lines are kept\n\n\n", + 'dummy' => 'value' + ) +--- +test: Three trailing newlines in literals +brief: > + To give you more control over how space + is preserved in text blocks, YAML has + the keep '+' and chomp '-' indicators. + The keep indicator will preserve all + ending newlines, while the chomp indicator + will strip all ending newlines. +yaml: | + clipped: | + This has one newline. + + + + same as "clipped" above: "This has one newline.\n" + + stripped: |- + This has no newline. + + + + same as "stripped" above: "This has no newline." + + kept: |+ + This has four newlines. + + + + same as "kept" above: "This has four newlines.\n\n\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has four newlines.\n\n\n\n", + 'same as "kept" above' => "This has four newlines.\n\n\n\n" + ) +--- +test: Extra trailing newlines with spaces +todo: true +brief: > + Normally, only a single newline is kept + from the end of a literal block, unless the + keep '+' character is used in combination + with the pipe. The following example + will preserve all ending whitespace + since the last line of both literal blocks + contains spaces which extend past the indentation + level. +yaml: | + --- + this: | + Foo + + + kept: |+ + Foo + + +php: | + array('this' => "Foo\n\n \n", + 'kept' => "Foo\n\n \n" ) + +--- +test: Folded Block in a Sequence +brief: > + A greater-then character, followed by an indented + block of text is treated as a folded block, in + which lines of text separated by a single newline + are concatenated as a single line. +yaml: | + --- + - apple + - banana + - > + can't you see + the beauty of yaml? + hmm + - dog +php: | + array( + 'apple', + 'banana', + "can't you see the beauty of yaml? hmm\n", + 'dog' + ) +--- +test: Folded Block as a Mapping Value +brief: > + Both literal and folded blocks can be + used in collections, as values in a + sequence or a mapping. +yaml: | + --- + quote: > + Mark McGwire's + year was crippled + by a knee injury. + source: espn +php: | + array( + 'quote' => "Mark McGwire's year was crippled by a knee injury.\n", + 'source' => 'espn' + ) +--- +test: Three trailing newlines in folded blocks +brief: > + The keep and chomp indicators can also + be applied to folded blocks. +yaml: | + clipped: > + This has one newline. + + + + same as "clipped" above: "This has one newline.\n" + + stripped: >- + This has no newline. + + + + same as "stripped" above: "This has no newline." + + kept: >+ + This has four newlines. + + + + same as "kept" above: "This has four newlines.\n\n\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has four newlines.\n\n\n\n", + 'same as "kept" above' => "This has four newlines.\n\n\n\n" + ) diff --git a/plugins/sfYaml/test/fixtures/YtsNullsAndEmpties.yml b/plugins/sfYaml/test/fixtures/YtsNullsAndEmpties.yml new file mode 100755 index 0000000..4e0c8db --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsNullsAndEmpties.yml @@ -0,0 +1,45 @@ +--- %YAML:1.0 +test: Empty Sequence +brief: > + You can represent the empty sequence + with an empty inline sequence. +yaml: | + empty: [] +php: | + array('empty' => array()) +--- +test: Empty Mapping +brief: > + You can represent the empty mapping + with an empty inline mapping. +yaml: | + empty: {} +php: | + array('empty' => array()) +--- +test: Empty Sequence as Entire Document +yaml: | + [] +php: | + array() +--- +test: Empty Mapping as Entire Document +yaml: | + {} +php: | + array() +--- +test: Null as Document +yaml: | + ~ +php: | + null +--- +test: Empty String +brief: > + You can represent an empty string + with a pair of quotes. +yaml: | + '' +php: | + '' diff --git a/plugins/sfYaml/test/fixtures/YtsSpecificationExamples.yml b/plugins/sfYaml/test/fixtures/YtsSpecificationExamples.yml new file mode 100755 index 0000000..a1d300f --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsSpecificationExamples.yml @@ -0,0 +1,1695 @@ +--- %YAML:1.0 +test: Sequence of scalars +spec: 2.1 +yaml: | + - Mark McGwire + - Sammy Sosa + - Ken Griffey +php: | + array('Mark McGwire', 'Sammy Sosa', 'Ken Griffey') +--- +test: Mapping of scalars to scalars +spec: 2.2 +yaml: | + hr: 65 + avg: 0.278 + rbi: 147 +php: | + array('hr' => 65, 'avg' => 0.278, 'rbi' => 147) +--- +test: Mapping of scalars to sequences +spec: 2.3 +yaml: | + american: + - Boston Red Sox + - Detroit Tigers + - New York Yankees + national: + - New York Mets + - Chicago Cubs + - Atlanta Braves +php: | + array('american' => + array( 'Boston Red Sox', 'Detroit Tigers', + 'New York Yankees' ), + 'national' => + array( 'New York Mets', 'Chicago Cubs', + 'Atlanta Braves' ) + ) +--- +test: Sequence of mappings +spec: 2.4 +yaml: | + - + name: Mark McGwire + hr: 65 + avg: 0.278 + - + name: Sammy Sosa + hr: 63 + avg: 0.288 +php: | + array( + array('name' => 'Mark McGwire', 'hr' => 65, 'avg' => 0.278), + array('name' => 'Sammy Sosa', 'hr' => 63, 'avg' => 0.288) + ) +--- +test: Legacy A5 +todo: true +spec: legacy_A5 +yaml: | + ? + - New York Yankees + - Atlanta Braves + : + - 2001-07-02 + - 2001-08-12 + - 2001-08-14 + ? + - Detroit Tigers + - Chicago Cubs + : + - 2001-07-23 +perl-busted: > + YAML.pm will be able to emulate this behavior soon. In this regard + it may be somewhat more correct than Python's native behaviour which + can only use tuples as mapping keys. PyYAML will also need to figure + out some clever way to roundtrip structured keys. +python: | + [ + { + ('New York Yankees', 'Atlanta Braves'): + [yaml.timestamp('2001-07-02'), + yaml.timestamp('2001-08-12'), + yaml.timestamp('2001-08-14')], + ('Detroit Tigers', 'Chicago Cubs'): + [yaml.timestamp('2001-07-23')] + } + ] +ruby: | + { + [ 'New York Yankees', 'Atlanta Braves' ] => + [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ], + [ 'Detroit Tigers', 'Chicago Cubs' ] => + [ Date.new( 2001, 7, 23 ) ] + } +syck: | + struct test_node seq1[] = { + { T_STR, 0, "New York Yankees" }, + { T_STR, 0, "Atlanta Braves" }, + end_node + }; + struct test_node seq2[] = { + { T_STR, 0, "2001-07-02" }, + { T_STR, 0, "2001-08-12" }, + { T_STR, 0, "2001-08-14" }, + end_node + }; + struct test_node seq3[] = { + { T_STR, 0, "Detroit Tigers" }, + { T_STR, 0, "Chicago Cubs" }, + end_node + }; + struct test_node seq4[] = { + { T_STR, 0, "2001-07-23" }, + end_node + }; + struct test_node map[] = { + { T_SEQ, 0, 0, seq1 }, + { T_SEQ, 0, 0, seq2 }, + { T_SEQ, 0, 0, seq3 }, + { T_SEQ, 0, 0, seq4 }, + end_node + }; + struct test_node stream[] = { + { T_MAP, 0, 0, map }, + end_node + }; + +--- +test: Sequence of sequences +spec: 2.5 +yaml: | + - [ name , hr , avg ] + - [ Mark McGwire , 65 , 0.278 ] + - [ Sammy Sosa , 63 , 0.288 ] +php: | + array( + array( 'name', 'hr', 'avg' ), + array( 'Mark McGwire', 65, 0.278 ), + array( 'Sammy Sosa', 63, 0.288 ) + ) +--- +test: Mapping of mappings +todo: true +spec: 2.6 +yaml: | + Mark McGwire: {hr: 65, avg: 0.278} + Sammy Sosa: { + hr: 63, + avg: 0.288 + } +php: | + array( + 'Mark McGwire' => + array( 'hr' => 65, 'avg' => 0.278 ), + 'Sammy Sosa' => + array( 'hr' => 63, 'avg' => 0.288 ) + ) +--- +test: Two documents in a stream each with a leading comment +todo: true +spec: 2.7 +yaml: | + # Ranking of 1998 home runs + --- + - Mark McGwire + - Sammy Sosa + - Ken Griffey + + # Team ranking + --- + - Chicago Cubs + - St Louis Cardinals +ruby: | + y = YAML::Stream.new + y.add( [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ] ) + y.add( [ 'Chicago Cubs', 'St Louis Cardinals' ] ) +documents: 2 + +--- +test: Play by play feed from a game +todo: true +spec: 2.8 +yaml: | + --- + time: 20:03:20 + player: Sammy Sosa + action: strike (miss) + ... + --- + time: 20:03:47 + player: Sammy Sosa + action: grand slam + ... +perl: | + [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ] +documents: 2 + +--- +test: Single document with two comments +spec: 2.9 +yaml: | + hr: # 1998 hr ranking + - Mark McGwire + - Sammy Sosa + rbi: + # 1998 rbi ranking + - Sammy Sosa + - Ken Griffey +php: | + array( + 'hr' => array( 'Mark McGwire', 'Sammy Sosa' ), + 'rbi' => array( 'Sammy Sosa', 'Ken Griffey' ) + ) +--- +test: Node for Sammy Sosa appears twice in this document +spec: 2.10 +yaml: | + --- + hr: + - Mark McGwire + # Following node labeled SS + - &SS Sammy Sosa + rbi: + - *SS # Subsequent occurance + - Ken Griffey +php: | + array( + 'hr' => + array('Mark McGwire', 'Sammy Sosa'), + 'rbi' => + array('Sammy Sosa', 'Ken Griffey') + ) +--- +test: Mapping between sequences +todo: true +spec: 2.11 +yaml: | + ? # PLAY SCHEDULE + - Detroit Tigers + - Chicago Cubs + : + - 2001-07-23 + + ? [ New York Yankees, + Atlanta Braves ] + : [ 2001-07-02, 2001-08-12, + 2001-08-14 ] +ruby: | + { + [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ], + [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ] + } +syck: | + struct test_node seq1[] = { + { T_STR, 0, "New York Yankees" }, + { T_STR, 0, "Atlanta Braves" }, + end_node + }; + struct test_node seq2[] = { + { T_STR, 0, "2001-07-02" }, + { T_STR, 0, "2001-08-12" }, + { T_STR, 0, "2001-08-14" }, + end_node + }; + struct test_node seq3[] = { + { T_STR, 0, "Detroit Tigers" }, + { T_STR, 0, "Chicago Cubs" }, + end_node + }; + struct test_node seq4[] = { + { T_STR, 0, "2001-07-23" }, + end_node + }; + struct test_node map[] = { + { T_SEQ, 0, 0, seq3 }, + { T_SEQ, 0, 0, seq4 }, + { T_SEQ, 0, 0, seq1 }, + { T_SEQ, 0, 0, seq2 }, + end_node + }; + struct test_node stream[] = { + { T_MAP, 0, 0, map }, + end_node + }; + +--- +test: Sequence key shortcut +spec: 2.12 +yaml: | + --- + # products purchased + - item : Super Hoop + quantity: 1 + - item : Basketball + quantity: 4 + - item : Big Shoes + quantity: 1 +php: | + array ( + array ( + 'item' => 'Super Hoop', + 'quantity' => 1, + ), + array ( + 'item' => 'Basketball', + 'quantity' => 4, + ), + array ( + 'item' => 'Big Shoes', + 'quantity' => 1, + ) + ) +perl: | + [ + { item => 'Super Hoop', quantity => 1 }, + { item => 'Basketball', quantity => 4 }, + { item => 'Big Shoes', quantity => 1 } + ] + +ruby: | + [ + { 'item' => 'Super Hoop', 'quantity' => 1 }, + { 'item' => 'Basketball', 'quantity' => 4 }, + { 'item' => 'Big Shoes', 'quantity' => 1 } + ] +python: | + [ + { 'item': 'Super Hoop', 'quantity': 1 }, + { 'item': 'Basketball', 'quantity': 4 }, + { 'item': 'Big Shoes', 'quantity': 1 } + ] +syck: | + struct test_node map1[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Super Hoop" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "1" }, + end_node + }; + struct test_node map2[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Basketball" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "4" }, + end_node + }; + struct test_node map3[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Big Shoes" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "1" }, + end_node + }; + struct test_node seq[] = { + { T_MAP, 0, 0, map1 }, + { T_MAP, 0, 0, map2 }, + { T_MAP, 0, 0, map3 }, + end_node + }; + struct test_node stream[] = { + { T_SEQ, 0, 0, seq }, + end_node + }; + + +--- +test: Literal perserves newlines +todo: true +spec: 2.13 +yaml: | + # ASCII Art + --- | + \//||\/|| + // || ||_ +perl: | + "\\//||\\/||\n// || ||_\n" +ruby: | + "\\//||\\/||\n// || ||_\n" +python: | + [ + flushLeft( + """ + \//||\/|| + // || ||_ + """ + ) + ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "\\//||\\/||\n// || ||_\n" }, + end_node + }; + +--- +test: Folded treats newlines as a space +todo: true +spec: 2.14 +yaml: | + --- + Mark McGwire's + year was crippled + by a knee injury. +perl: | + "Mark McGwire's year was crippled by a knee injury." +ruby: | + "Mark McGwire's year was crippled by a knee injury." +python: | + [ "Mark McGwire's year was crippled by a knee injury." ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "Mark McGwire's year was crippled by a knee injury." }, + end_node + }; + +--- +test: Newlines preserved for indented and blank lines +todo: true +spec: 2.15 +yaml: | + --- > + Sammy Sosa completed another + fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year! +perl: | + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" +ruby: | + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" +python: | + [ + flushLeft( + """ + Sammy Sosa completed another fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year! + """ + ) + ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" }, + end_node + }; + + +--- +test: Indentation determines scope +spec: 2.16 +yaml: | + name: Mark McGwire + accomplishment: > + Mark set a major league + home run record in 1998. + stats: | + 65 Home Runs + 0.278 Batting Average +php: | + array( + 'name' => 'Mark McGwire', + 'accomplishment' => "Mark set a major league home run record in 1998.\n", + 'stats' => "65 Home Runs\n0.278 Batting Average\n" + ) +--- +test: Quoted scalars +todo: true +spec: 2.17 +yaml: | + unicode: "Sosa did fine.\u263A" + control: "\b1998\t1999\t2000\n" + hexesc: "\x0D\x0A is \r\n" + + single: '"Howdy!" he cried.' + quoted: ' # not a ''comment''.' + tie-fighter: '|\-*-/|' +ruby: | + { + "tie-fighter" => "|\\-*-/|", + "control"=>"\0101998\t1999\t2000\n", + "unicode"=>"Sosa did fine." + ["263A".hex ].pack('U*'), + "quoted"=>" # not a 'comment'.", + "single"=>"\"Howdy!\" he cried.", + "hexesc"=>"\r\n is \r\n" + } +--- +test: Multiline flow scalars +todo: true +spec: 2.18 +yaml: | + plain: + This unquoted scalar + spans many lines. + + quoted: "So does this + quoted scalar.\n" +ruby: | + { + 'plain' => 'This unquoted scalar spans many lines.', + 'quoted' => "So does this quoted scalar.\n" + } +--- +test: Integers +spec: 2.19 +yaml: | + canonical: 12345 + decimal: +12,345 + octal: 014 + hexadecimal: 0xC +php: | + array( + 'canonical' => 12345, + 'decimal' => 12345, + 'octal' => 014, + 'hexadecimal' => 0xC + ) +--- +# FIX: spec shows parens around -inf and NaN +test: Floating point +spec: 2.20 +yaml: | + canonical: 1.23015e+3 + exponential: 12.3015e+02 + fixed: 1,230.15 + negative infinity: -.inf + not a number: .NaN +php: | + array( + 'canonical' => 1230.15, + 'exponential' => 1230.15, + 'fixed' => 1230.15, + 'negative infinity' => log(0), + 'not a number' => -log(0), + ) +--- +test: Miscellaneous +spec: 2.21 +yaml: | + null: ~ + true: y + false: n + string: '12345' +php: | + array( + '' => null, + 1 => true, + 0 => false, + 'string' => '12345' + ) +--- +test: Timestamps +todo: true +spec: 2.22 +yaml: | + canonical: 2001-12-15T02:59:43.1Z + iso8601: 2001-12-14t21:59:43.10-05:00 + spaced: 2001-12-14 21:59:43.10 -05:00 + date: 2002-12-14 # Time is noon UTC +php: | + array( + 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ), + 'iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'date' => Date.new( 2002, 12, 14 ) + ) +--- +test: legacy Timestamps test +todo: true +spec: legacy D4 +yaml: | + canonical: 2001-12-15T02:59:43.00Z + iso8601: 2001-02-28t21:59:43.00-05:00 + spaced: 2001-12-14 21:59:43.00 -05:00 + date: 2002-12-14 +php: | + array( + 'canonical' => Time::utc( 2001, 12, 15, 2, 59, 43, 0 ), + 'iso8601' => YAML::mktime( 2001, 2, 28, 21, 59, 43, 0, "-05:00" ), + 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0, "-05:00" ), + 'date' => Date.new( 2002, 12, 14 ) + ) +--- +test: Various explicit families +todo: true +spec: 2.23 +yaml: | + not-date: !str 2002-04-28 + picture: !binary | + R0lGODlhDAAMAIQAAP//9/X + 17unp5WZmZgAAAOfn515eXv + Pz7Y6OjuDg4J+fn5OTk6enp + 56enmleECcgggoBADs= + + application specific tag: !!something | + The semantics of the tag + above may be different for + different documents. + +ruby-setup: | + YAML.add_private_type( "something" ) do |type, val| + "SOMETHING: #{val}" + end +ruby: | + { + 'not-date' => '2002-04-28', + 'picture' => "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236i^\020' \202\n\001\000;", + 'application specific tag' => "SOMETHING: The semantics of the tag\nabove may be different for\ndifferent documents.\n" + } +--- +test: Application specific family +todo: true +spec: 2.24 +yaml: | + # Establish a tag prefix + --- !clarkevans.com,2002/graph/^shape + # Use the prefix: shorthand for + # !clarkevans.com,2002/graph/circle + - !^circle + center: &ORIGIN {x: 73, 'y': 129} + radius: 7 + - !^line # !clarkevans.com,2002/graph/line + start: *ORIGIN + finish: { x: 89, 'y': 102 } + - !^label + start: *ORIGIN + color: 0xFFEEBB + value: Pretty vector drawing. +ruby-setup: | + YAML.add_domain_type( "clarkevans.com,2002", 'graph/shape' ) { |type, val| + if Array === val + val << "Shape Container" + val + else + raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect + end + } + one_shape_proc = Proc.new { |type, val| + scheme, domain, type = type.split( /:/, 3 ) + if val.is_a? ::Hash + val['TYPE'] = "Shape: #{type}" + val + else + raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect + end + } + YAML.add_domain_type( "clarkevans.com,2002", 'graph/circle', &one_shape_proc ) + YAML.add_domain_type( "clarkevans.com,2002", 'graph/line', &one_shape_proc ) + YAML.add_domain_type( "clarkevans.com,2002", 'graph/label', &one_shape_proc ) +ruby: | + [ + { + "radius" => 7, + "center"=> + { + "x" => 73, + "y" => 129 + }, + "TYPE" => "Shape: graph/circle" + }, { + "finish" => + { + "x" => 89, + "y" => 102 + }, + "TYPE" => "Shape: graph/line", + "start" => + { + "x" => 73, + "y" => 129 + } + }, { + "TYPE" => "Shape: graph/label", + "value" => "Pretty vector drawing.", + "start" => + { + "x" => 73, + "y" => 129 + }, + "color" => 16772795 + }, + "Shape Container" + ] +# --- +# test: Unordered set +# spec: 2.25 +# yaml: | +# # sets are represented as a +# # mapping where each key is +# # associated with the empty string +# --- !set +# ? Mark McGwire +# ? Sammy Sosa +# ? Ken Griff +--- +test: Ordered mappings +todo: true +spec: 2.26 +yaml: | + # ordered maps are represented as + # a sequence of mappings, with + # each mapping having one key + --- !omap + - Mark McGwire: 65 + - Sammy Sosa: 63 + - Ken Griffy: 58 +ruby: | + YAML::Omap[ + 'Mark McGwire', 65, + 'Sammy Sosa', 63, + 'Ken Griffy', 58 + ] +--- +test: Invoice +dump_skip: true +spec: 2.27 +yaml: | + --- !clarkevans.com,2002/^invoice + invoice: 34843 + date : 2001-01-23 + bill-to: &id001 + given : Chris + family : Dumars + address: + lines: | + 458 Walkman Dr. + Suite #292 + city : Royal Oak + state : MI + postal : 48046 + ship-to: *id001 + product: + - + sku : BL394D + quantity : 4 + description : Basketball + price : 450.00 + - + sku : BL4438H + quantity : 1 + description : Super Hoop + price : 2392.00 + tax : 251.42 + total: 4443.52 + comments: > + Late afternoon is best. + Backup contact is Nancy + Billsmer @ 338-4338. +php: | + array( + 'invoice' => 34843, 'date' => mktime(0, 0, 0, 1, 23, 2001), + 'bill-to' => + array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) ) + , 'ship-to' => + array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) ) + , 'product' => + array( + array( 'sku' => 'BL394D', 'quantity' => 4, 'description' => 'Basketball', 'price' => 450.00 ), + array( 'sku' => 'BL4438H', 'quantity' => 1, 'description' => 'Super Hoop', 'price' => 2392.00 ) + ), + 'tax' => 251.42, 'total' => 4443.52, + 'comments' => "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\n" + ) +--- +test: Log file +todo: true +spec: 2.28 +yaml: | + --- + Time: 2001-11-23 15:01:42 -05:00 + User: ed + Warning: > + This is an error message + for the log file + --- + Time: 2001-11-23 15:02:31 -05:00 + User: ed + Warning: > + A slightly different error + message. + --- + Date: 2001-11-23 15:03:17 -05:00 + User: ed + Fatal: > + Unknown variable "bar" + Stack: + - file: TopClass.py + line: 23 + code: | + x = MoreObject("345\n") + - file: MoreClass.py + line: 58 + code: |- + foo = bar +ruby: | + y = YAML::Stream.new + y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 01, 42, 00, "-05:00" ), + 'User' => 'ed', 'Warning' => "This is an error message for the log file\n" } ) + y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 02, 31, 00, "-05:00" ), + 'User' => 'ed', 'Warning' => "A slightly different error message.\n" } ) + y.add( { 'Date' => YAML::mktime( 2001, 11, 23, 15, 03, 17, 00, "-05:00" ), + 'User' => 'ed', 'Fatal' => "Unknown variable \"bar\"\n", + 'Stack' => [ + { 'file' => 'TopClass.py', 'line' => 23, 'code' => "x = MoreObject(\"345\\n\")\n" }, + { 'file' => 'MoreClass.py', 'line' => 58, 'code' => "foo = bar" } ] } ) +documents: 3 + +--- +test: Throwaway comments +yaml: | + ### These are four throwaway comment ### + + ### lines (the second line is empty). ### + this: | # Comments may trail lines. + contains three lines of text. + The third one starts with a + # character. This isn't a comment. + + # These are three throwaway comment + # lines (the first line is empty). +php: | + array( + 'this' => "contains three lines of text.\nThe third one starts with a\n# character. This isn't a comment.\n" + ) +--- +test: Document with a single value +todo: true +yaml: | + --- > + This YAML stream contains a single text value. + The next stream is a log file - a sequence of + log entries. Adding an entry to the log is a + simple matter of appending it at the end. +ruby: | + "This YAML stream contains a single text value. The next stream is a log file - a sequence of log entries. Adding an entry to the log is a simple matter of appending it at the end.\n" +--- +test: Document stream +todo: true +yaml: | + --- + at: 2001-08-12 09:25:00.00 Z + type: GET + HTTP: '1.0' + url: '/index.html' + --- + at: 2001-08-12 09:25:10.00 Z + type: GET + HTTP: '1.0' + url: '/toc.html' +ruby: | + y = YAML::Stream.new + y.add( { + 'at' => Time::utc( 2001, 8, 12, 9, 25, 00 ), + 'type' => 'GET', + 'HTTP' => '1.0', + 'url' => '/index.html' + } ) + y.add( { + 'at' => Time::utc( 2001, 8, 12, 9, 25, 10 ), + 'type' => 'GET', + 'HTTP' => '1.0', + 'url' => '/toc.html' + } ) +documents: 2 + +--- +test: Top level mapping +yaml: | + # This stream is an example of a top-level mapping. + invoice : 34843 + date : 2001-01-23 + total : 4443.52 +php: | + array( + 'invoice' => 34843, + 'date' => mktime(0, 0, 0, 1, 23, 2001), + 'total' => 4443.52 + ) +--- +test: Single-line documents +todo: true +yaml: | + # The following is a sequence of three documents. + # The first contains an empty mapping, the second + # an empty sequence, and the last an empty string. + --- {} + --- [ ] + --- '' +ruby: | + y = YAML::Stream.new + y.add( {} ) + y.add( [] ) + y.add( '' ) +documents: 3 + +--- +test: Document with pause +todo: true +yaml: | + # A communication channel based on a YAML stream. + --- + sent at: 2002-06-06 11:46:25.10 Z + payload: Whatever + # Receiver can process this as soon as the following is sent: + ... + # Even if the next message is sent long after: + --- + sent at: 2002-06-06 12:05:53.47 Z + payload: Whatever + ... +ruby: | + y = YAML::Stream.new + y.add( + { 'sent at' => YAML::mktime( 2002, 6, 6, 11, 46, 25, 0.10 ), + 'payload' => 'Whatever' } + ) + y.add( + { "payload" => "Whatever", "sent at" => YAML::mktime( 2002, 6, 6, 12, 5, 53, 0.47 ) } + ) +documents: 2 + +--- +test: Explicit typing +yaml: | + integer: 12 + also int: ! "12" + string: !str 12 +php: | + array( 'integer' => 12, 'also int' => 12, 'string' => '12' ) +--- +test: Private types +todo: true +yaml: | + # Both examples below make use of the 'x-private:ball' + # type family URI, but with different semantics. + --- + pool: !!ball + number: 8 + color: black + --- + bearing: !!ball + material: steel +ruby: | + y = YAML::Stream.new + y.add( { 'pool' => + YAML::PrivateType.new( 'ball', + { 'number' => 8, 'color' => 'black' } ) } + ) + y.add( { 'bearing' => + YAML::PrivateType.new( 'ball', + { 'material' => 'steel' } ) } + ) +documents: 2 + +--- +test: Type family under yaml.org +yaml: | + # The URI is 'tag:yaml.org,2002:str' + - !str a Unicode string +php: | + array( 'a Unicode string' ) +--- +test: Type family under perl.yaml.org +todo: true +yaml: | + # The URI is 'tag:perl.yaml.org,2002:Text::Tabs' + - !perl/Text::Tabs {} +ruby: | + [ YAML::DomainType.new( 'perl.yaml.org,2002', 'Text::Tabs', {} ) ] +--- +test: Type family under clarkevans.com +todo: true +yaml: | + # The URI is 'tag:clarkevans.com,2003-02:timesheet' + - !clarkevans.com,2003-02/timesheet {} +ruby: | + [ YAML::DomainType.new( 'clarkevans.com,2003-02', 'timesheet', {} ) ] +--- +test: URI Escaping +todo: true +yaml: | + same: + - !domain.tld,2002/type\x30 value + - !domain.tld,2002/type0 value + different: # As far as the YAML parser is concerned + - !domain.tld,2002/type%30 value + - !domain.tld,2002/type0 value +ruby-setup: | + YAML.add_domain_type( "domain.tld,2002", "type0" ) { |type, val| + "ONE: #{val}" + } + YAML.add_domain_type( "domain.tld,2002", "type%30" ) { |type, val| + "TWO: #{val}" + } +ruby: | + { 'same' => [ 'ONE: value', 'ONE: value' ], 'different' => [ 'TWO: value', 'ONE: value' ] } +--- +test: URI Prefixing +todo: true +yaml: | + # 'tag:domain.tld,2002:invoice' is some type family. + invoice: !domain.tld,2002/^invoice + # 'seq' is shorthand for 'tag:yaml.org,2002:seq'. + # This does not effect '^customer' below + # because it is does not specify a prefix. + customers: !seq + # '^customer' is shorthand for the full + # notation 'tag:domain.tld,2002:customer'. + - !^customer + given : Chris + family : Dumars +ruby-setup: | + YAML.add_domain_type( "domain.tld,2002", /(invoice|customer)/ ) { |type, val| + if val.is_a? ::Hash + scheme, domain, type = type.split( /:/, 3 ) + val['type'] = "domain #{type}" + val + else + raise YAML::Error, "Not a Hash in domain.tld/invoice: " + val.inspect + end + } +ruby: | + { "invoice"=> { "customers"=> [ { "given"=>"Chris", "type"=>"domain customer", "family"=>"Dumars" } ], "type"=>"domain invoice" } } + +--- +test: Overriding anchors +yaml: | + anchor : &A001 This scalar has an anchor. + override : &A001 > + The alias node below is a + repeated use of this value. + alias : *A001 +php: | + array( 'anchor' => 'This scalar has an anchor.', + 'override' => "The alias node below is a repeated use of this value.\n", + 'alias' => "The alias node below is a repeated use of this value.\n" ) +--- +test: Flow and block formatting +todo: true +yaml: | + empty: [] + flow: [ one, two, three # May span lines, + , four, # indentation is + five ] # mostly ignored. + block: + - First item in top sequence + - + - Subordinate sequence entry + - > + A folded sequence entry + - Sixth item in top sequence +ruby: | + { 'empty' => [], 'flow' => [ 'one', 'two', 'three', 'four', 'five' ], + 'block' => [ 'First item in top sequence', [ 'Subordinate sequence entry' ], + "A folded sequence entry\n", 'Sixth item in top sequence' ] } +--- +test: Complete mapping test +todo: true +yaml: | + empty: {} + flow: { one: 1, two: 2 } + spanning: { one: 1, + two: 2 } + block: + first : First entry + second: + key: Subordinate mapping + third: + - Subordinate sequence + - { } + - Previous mapping is empty. + - A key: value pair in a sequence. + A second: key:value pair. + - The previous entry is equal to the following one. + - + A key: value pair in a sequence. + A second: key:value pair. + !float 12 : This key is a float. + ? > + ? + : This key had to be protected. + "\a" : This key had to be escaped. + ? > + This is a + multi-line + folded key + : Whose value is + also multi-line. + ? this also works as a key + : with a value at the next line. + ? + - This key + - is a sequence + : + - With a sequence value. + ? + This: key + is a: mapping + : + with a: mapping value. +ruby: | + { 'empty' => {}, 'flow' => { 'one' => 1, 'two' => 2 }, + 'spanning' => { 'one' => 1, 'two' => 2 }, + 'block' => { 'first' => 'First entry', 'second' => + { 'key' => 'Subordinate mapping' }, 'third' => + [ 'Subordinate sequence', {}, 'Previous mapping is empty.', + { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' }, + 'The previous entry is equal to the following one.', + { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' } ], + 12.0 => 'This key is a float.', "?\n" => 'This key had to be protected.', + "\a" => 'This key had to be escaped.', + "This is a multi-line folded key\n" => "Whose value is also multi-line.", + 'this also works as a key' => 'with a value at the next line.', + [ 'This key', 'is a sequence' ] => [ 'With a sequence value.' ] } } + # Couldn't recreate map exactly, so we'll do a detailed check to be sure it's entact + obj_y['block'].keys.each { |k| + if Hash === k + v = obj_y['block'][k] + if k['This'] == 'key' and k['is a'] == 'mapping' and v['with a'] == 'mapping value.' + obj_r['block'][k] = v + end + end + } +--- +test: Literal explicit indentation +yaml: | + # Explicit indentation must + # be given in all the three + # following cases. + leading spaces: |2 + This value starts with four spaces. + + leading line break: |2 + + This value starts with a line break. + + leading comment indicator: |2 + # first line starts with a + # character. + + # Explicit indentation may + # also be given when it is + # not required. + redundant: |2 + This value is indented 2 spaces. +php: | + array( + 'leading spaces' => " This value starts with four spaces.\n", + 'leading line break' => "\nThis value starts with a line break.\n", + 'leading comment indicator' => "# first line starts with a\n# character.\n", + 'redundant' => "This value is indented 2 spaces.\n" + ) +--- +test: Chomping and keep modifiers +yaml: | + clipped: | + This has one newline. + + same as "clipped" above: "This has one newline.\n" + + stripped: |- + This has no newline. + + same as "stripped" above: "This has no newline." + + kept: |+ + This has two newlines. + + same as "kept" above: "This has two newlines.\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has two newlines.\n\n", + 'same as "kept" above' => "This has two newlines.\n\n" + ) +--- +test: Literal combinations +todo: true +yaml: | + empty: | + + literal: | + The \ ' " characters may be + freely used. Leading white + space is significant. + + Line breaks are significant. + Thus this value contains one + empty line and ends with a + single line break, but does + not start with one. + + is equal to: "The \\ ' \" characters may \ + be\nfreely used. Leading white\n space \ + is significant.\n\nLine breaks are \ + significant.\nThus this value contains \ + one\nempty line and ends with a\nsingle \ + line break, but does\nnot start with one.\n" + + # Comments may follow a block + # scalar value. They must be + # less indented. + + # Modifiers may be combined in any order. + indented and chomped: |2- + This has no newline. + + also written as: |-2 + This has no newline. + + both are equal to: " This has no newline." +php: | + array( + 'empty' => '', + 'literal' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " + + "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" + + "empty line and ends with a\nsingle line break, but does\nnot start with one.\n", + 'is equal to' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " + + "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" + + "empty line and ends with a\nsingle line break, but does\nnot start with one.\n", + 'indented and chomped' => ' This has no newline.', + 'also written as' => ' This has no newline.', + 'both are equal to' => ' This has no newline.' + ) +--- +test: Folded combinations +todo: true +yaml: | + empty: > + + one paragraph: > + Line feeds are converted + to spaces, so this value + contains no line breaks + except for the final one. + + multiple paragraphs: >2 + + An empty line, either + at the start or in + the value: + + Is interpreted as a + line break. Thus this + value contains three + line breaks. + + indented text: > + This is a folded + paragraph followed + by a list: + * first entry + * second entry + Followed by another + folded paragraph, + another list: + + * first entry + + * second entry + + And a final folded + paragraph. + + above is equal to: | + This is a folded paragraph followed by a list: + * first entry + * second entry + Followed by another folded paragraph, another list: + + * first entry + + * second entry + + And a final folded paragraph. + + # Explicit comments may follow + # but must be less indented. +php: | + array( + 'empty' => '', + 'one paragraph' => 'Line feeds are converted to spaces, so this value'. + " contains no line breaks except for the final one.\n", + 'multiple paragraphs' => "\nAn empty line, either at the start or in the value:\n". + "Is interpreted as a line break. Thus this value contains three line breaks.\n", + 'indented text' => "This is a folded paragraph followed by a list:\n". + " * first entry\n * second entry\nFollowed by another folded paragraph, ". + "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n", + 'above is equal to' => "This is a folded paragraph followed by a list:\n". + " * first entry\n * second entry\nFollowed by another folded paragraph, ". + "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n" + ) +--- +test: Single quotes +todo: true +yaml: | + empty: '' + second: '! : \ etc. can be used freely.' + third: 'a single quote '' must be escaped.' + span: 'this contains + six spaces + + and one + line break' + is same as: "this contains six spaces\nand one line break" +php: | + array( + 'empty' => '', + 'second' => '! : \\ etc. can be used freely.', + 'third' => "a single quote ' must be escaped.", + 'span' => "this contains six spaces\nand one line break", + 'is same as' => "this contains six spaces\nand one line break" + ) +--- +test: Double quotes +todo: true +yaml: | + empty: "" + second: "! : etc. can be used freely." + third: "a \" or a \\ must be escaped." + fourth: "this value ends with an LF.\n" + span: "this contains + four \ + spaces" + is equal to: "this contains four spaces" +php: | + array( + 'empty' => '', + 'second' => '! : etc. can be used freely.', + 'third' => 'a " or a \\ must be escaped.', + 'fourth' => "this value ends with an LF.\n", + 'span' => "this contains four spaces", + 'is equal to' => "this contains four spaces" + ) +--- +test: Unquoted strings +todo: true +yaml: | + first: There is no unquoted empty string. + + second: 12 ## This is an integer. + + third: !str 12 ## This is a string. + + span: this contains + six spaces + + and one + line break + + indicators: this has no comments. + #:foo and bar# are + both text. + + flow: [ can span + lines, # comment + like + this ] + + note: { one-line keys: but multi-line values } + +php: | + array( + 'first' => 'There is no unquoted empty string.', + 'second' => 12, + 'third' => '12', + 'span' => "this contains six spaces\nand one line break", + 'indicators' => "this has no comments. #:foo and bar# are both text.", + 'flow' => [ 'can span lines', 'like this' ], + 'note' => { 'one-line keys' => 'but multi-line values' } + ) +--- +test: Spanning sequences +todo: true +yaml: | + # The following are equal seqs + # with different identities. + flow: [ one, two ] + spanning: [ one, + two ] + block: + - one + - two +php: | + array( + 'flow' => [ 'one', 'two' ], + 'spanning' => [ 'one', 'two' ], + 'block' => [ 'one', 'two' ] + ) +--- +test: Flow mappings +yaml: | + # The following are equal maps + # with different identities. + flow: { one: 1, two: 2 } + block: + one: 1 + two: 2 +php: | + array( + 'flow' => array( 'one' => 1, 'two' => 2 ), + 'block' => array( 'one' => 1, 'two' => 2 ) + ) +--- +test: Representations of 12 +todo: true +yaml: | + - 12 # An integer + # The following scalars + # are loaded to the + # string value '1' '2'. + - !str 12 + - '12' + - "12" + - "\ + 1\ + 2\ + " + # Strings containing paths and regexps can be unquoted: + - /foo/bar + - d:/foo/bar + - foo/bar + - /a.*b/ +php: | + array( 12, '12', '12', '12', '12', '/foo/bar', 'd:/foo/bar', 'foo/bar', '/a.*b/' ) +--- +test: "Null" +todo: true +yaml: | + canonical: ~ + + english: null + + # This sequence has five + # entries, two with values. + sparse: + - ~ + - 2nd entry + - Null + - 4th entry + - + + four: This mapping has five keys, + only two with values. + +php: | + array ( + 'canonical' => null, + 'english' => null, + 'sparse' => array( null, '2nd entry', null, '4th entry', null ]), + 'four' => 'This mapping has five keys, only two with values.' + ) +--- +test: Omap +todo: true +yaml: | + # Explicitly typed dictionary. + Bestiary: !omap + - aardvark: African pig-like ant eater. Ugly. + - anteater: South-American ant eater. Two species. + - anaconda: South-American constrictor snake. Scary. + # Etc. +ruby: | + { + 'Bestiary' => YAML::Omap[ + 'aardvark', 'African pig-like ant eater. Ugly.', + 'anteater', 'South-American ant eater. Two species.', + 'anaconda', 'South-American constrictor snake. Scary.' + ] + } + +--- +test: Pairs +todo: true +yaml: | + # Explicitly typed pairs. + tasks: !pairs + - meeting: with team. + - meeting: with boss. + - break: lunch. + - meeting: with client. +ruby: | + { + 'tasks' => YAML::Pairs[ + 'meeting', 'with team.', + 'meeting', 'with boss.', + 'break', 'lunch.', + 'meeting', 'with client.' + ] + } + +--- +test: Set +todo: true +yaml: | + # Explicitly typed set. + baseball players: !set + Mark McGwire: + Sammy Sosa: + Ken Griffey: +ruby: | + { + 'baseball players' => YAML::Set[ + 'Mark McGwire', nil, + 'Sammy Sosa', nil, + 'Ken Griffey', nil + ] + } + +--- +test: Boolean +yaml: | + false: used as key + logical: true + answer: no +php: | + array( + false => 'used as key', + 'logical' => true, + 'answer' => false + ) +--- +test: Integer +yaml: | + canonical: 12345 + decimal: +12,345 + octal: 014 + hexadecimal: 0xC +php: | + array( + 'canonical' => 12345, + 'decimal' => 12345, + 'octal' => 12, + 'hexadecimal' => 12 + ) +--- +test: Float +yaml: | + canonical: 1.23015e+3 + exponential: 12.3015e+02 + fixed: 1,230.15 + negative infinity: -.inf + not a number: .NaN +php: | + array( + 'canonical' => 1230.15, + 'exponential' => 1230.15, + 'fixed' => 1230.15, + 'negative infinity' => log(0), + 'not a number' => -log(0) + ) +--- +test: Timestamp +todo: true +yaml: | + canonical: 2001-12-15T02:59:43.1Z + valid iso8601: 2001-12-14t21:59:43.10-05:00 + space separated: 2001-12-14 21:59:43.10 -05:00 + date (noon UTC): 2002-12-14 +ruby: | + array( + 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ), + 'valid iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'space separated' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'date (noon UTC)' => Date.new( 2002, 12, 14 ) + ) +--- +test: Binary +todo: true +yaml: | + canonical: !binary "\ + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\ + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\ + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=" + base64: !binary | + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= + description: > + The binary value above is a tiny arrow + encoded as a gif image. +ruby-setup: | + arrow_gif = "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236iiiccc\243\243\243\204\204\204\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371!\376\016Made with GIMP\000,\000\000\000\000\f\000\f\000\000\005, \216\2010\236\343@\024\350i\020\304\321\212\010\034\317\200M$z\357\3770\205p\270\2601f\r\e\316\001\303\001\036\020' \202\n\001\000;" +ruby: | + { + 'canonical' => arrow_gif, + 'base64' => arrow_gif, + 'description' => "The binary value above is a tiny arrow encoded as a gif image.\n" + } + +--- +test: Merge key +todo: true +yaml: | + --- + - &CENTER { x: 1, y: 2 } + - &LEFT { x: 0, y: 2 } + - &BIG { r: 10 } + - &SMALL { r: 1 } + + # All the following maps are equal: + + - # Explicit keys + x: 1 + y: 2 + r: 10 + label: center/big + + - # Merge one map + << : *CENTER + r: 10 + label: center/big + + - # Merge multiple maps + << : [ *CENTER, *BIG ] + label: center/big + + - # Override + << : [ *BIG, *LEFT, *SMALL ] + x: 1 + label: center/big + +ruby-setup: | + center = { 'x' => 1, 'y' => 2 } + left = { 'x' => 0, 'y' => 2 } + big = { 'r' => 10 } + small = { 'r' => 1 } + node1 = { 'x' => 1, 'y' => 2, 'r' => 10, 'label' => 'center/big' } + node2 = center.dup + node2.update( { 'r' => 10, 'label' => 'center/big' } ) + node3 = big.dup + node3.update( center ) + node3.update( { 'label' => 'center/big' } ) + node4 = small.dup + node4.update( left ) + node4.update( big ) + node4.update( { 'x' => 1, 'label' => 'center/big' } ) + +ruby: | + [ + center, left, big, small, node1, node2, node3, node4 + ] + +--- +test: Default key +todo: true +yaml: | + --- # Old schema + link with: + - library1.dll + - library2.dll + --- # New schema + link with: + - = : library1.dll + version: 1.2 + - = : library2.dll + version: 2.3 +ruby: | + y = YAML::Stream.new + y.add( { 'link with' => [ 'library1.dll', 'library2.dll' ] } ) + obj_h = Hash[ 'version' => 1.2 ] + obj_h.default = 'library1.dll' + obj_h2 = Hash[ 'version' => 2.3 ] + obj_h2.default = 'library2.dll' + y.add( { 'link with' => [ obj_h, obj_h2 ] } ) +documents: 2 + +--- +test: Special keys +todo: true +yaml: | + "!": These three keys + "&": had to be quoted + "=": and are normal strings. + # NOTE: the following node should NOT be serialized this way. + encoded node : + !special '!' : '!type' + !special|canonical '&' : 12 + = : value + # The proper way to serialize the above node is as follows: + node : !!type &12 value +ruby: | + { '!' => 'These three keys', '&' => 'had to be quoted', + '=' => 'and are normal strings.', + 'encoded node' => YAML::PrivateType.new( 'type', 'value' ), + 'node' => YAML::PrivateType.new( 'type', 'value' ) } diff --git a/plugins/sfYaml/test/fixtures/YtsTypeTransfers.yml b/plugins/sfYaml/test/fixtures/YtsTypeTransfers.yml new file mode 100755 index 0000000..398fa0e --- /dev/null +++ b/plugins/sfYaml/test/fixtures/YtsTypeTransfers.yml @@ -0,0 +1,244 @@ +--- %YAML:1.0 +test: Strings +brief: > + Any group of characters beginning with an + alphabetic or numeric character is a string, + unless it belongs to one of the groups below + (such as an Integer or Time). +yaml: | + String +php: | + 'String' +--- +test: String characters +brief: > + A string can contain any alphabetic or + numeric character, along with many + punctuation characters, including the + period, dash, space, quotes, exclamation, and + question mark. +yaml: | + - What's Yaml? + - It's for writing data structures in plain text. + - And? + - And what? That's not good enough for you? + - No, I mean, "And what about Yaml?" + - Oh, oh yeah. Uh.. Yaml for Ruby. +php: | + array( + "What's Yaml?", + "It's for writing data structures in plain text.", + "And?", + "And what? That's not good enough for you?", + "No, I mean, \"And what about Yaml?\"", + "Oh, oh yeah. Uh.. Yaml for Ruby." + ) +--- +test: Indicators in Strings +brief: > + Be careful using indicators in strings. In particular, + the comma, colon, and pound sign must be used carefully. +yaml: | + the colon followed by space is an indicator: but is a string:right here + same for the pound sign: here we have it#in a string + the comma can, honestly, be used in most cases: [ but not in, inline collections ] +php: | + array( + 'the colon followed by space is an indicator' => 'but is a string:right here', + 'same for the pound sign' => 'here we have it#in a string', + 'the comma can, honestly, be used in most cases' => array('but not in', 'inline collections') + ) +--- +test: Forcing Strings +brief: > + Any YAML type can be forced into a string using the + explicit !str method. +yaml: | + date string: !str 2001-08-01 + number string: !str 192 +php: | + array( + 'date string' => '2001-08-01', + 'number string' => '192' + ) +--- +test: Single-quoted Strings +brief: > + You can also enclose your strings within single quotes, + which allows use of slashes, colons, and other indicators + freely. Inside single quotes, you can represent a single + quote in your string by using two single quotes next to + each other. +yaml: | + all my favorite symbols: '#:!/%.)' + a few i hate: '&(*' + why do i hate them?: 'it''s very hard to explain' + entities: '£ me' +php: | + array( + 'all my favorite symbols' => '#:!/%.)', + 'a few i hate' => '&(*', + 'why do i hate them?' => 'it\'s very hard to explain', + 'entities' => '£ me' + ) +--- +test: Double-quoted Strings +brief: > + Enclosing strings in double quotes allows you + to use escapings to represent ASCII and + Unicode characters. +yaml: | + i know where i want my line breaks: "one here\nand another here\n" +php: | + array( + 'i know where i want my line breaks' => "one here\nand another here\n" + ) +--- +test: Multi-line Quoted Strings +todo: true +brief: > + Both single- and double-quoted strings may be + carried on to new lines in your YAML document. + They must be indented a step and indentation + is interpreted as a single space. +yaml: | + i want a long string: "so i'm going to + let it go on and on to other lines + until i end it with a quote." +php: | + array('i want a long string' => "so i'm going to ". + "let it go on and on to other lines ". + "until i end it with a quote." + ) + +--- +test: Plain scalars +todo: true +brief: > + Unquoted strings may also span multiple lines, if they + are free of YAML space indicators and indented. +yaml: | + - My little toe is broken in two places; + - I'm crazy to have skied this way; + - I'm not the craziest he's seen, since there was always the German guy + who skied for 3 hours on a broken shin bone (just below the kneecap); + - Nevertheless, second place is respectable, and he doesn't + recommend going for the record; + - He's going to put my foot in plaster for a month; + - This would impair my skiing ability somewhat for the + duration, as can be imagined. +php: | + array( + "My little toe is broken in two places;", + "I'm crazy to have skied this way;", + "I'm not the craziest he's seen, since there was always ". + "the German guy who skied for 3 hours on a broken shin ". + "bone (just below the kneecap);", + "Nevertheless, second place is respectable, and he doesn't ". + "recommend going for the record;", + "He's going to put my foot in plaster for a month;", + "This would impair my skiing ability somewhat for the duration, ". + "as can be imagined." + ) +--- +test: 'Null' +brief: > + You can use the tilde '~' character for a null value. +yaml: | + name: Mr. Show + hosted by: Bob and David + date of next season: ~ +php: | + array( + 'name' => 'Mr. Show', + 'hosted by' => 'Bob and David', + 'date of next season' => null + ) +--- +test: Boolean +brief: > + You can use 'true' and 'false' for boolean values. +yaml: | + Is Gus a Liar?: true + Do I rely on Gus for Sustenance?: false +php: | + array( + 'Is Gus a Liar?' => true, + 'Do I rely on Gus for Sustenance?' => false + ) +--- +test: Integers +dump_skip: true +brief: > + An integer is a series of numbers, optionally + starting with a positive or negative sign. Integers + may also contain commas for readability. +yaml: | + zero: 0 + simple: 12 + one-thousand: 1,000 + negative one-thousand: -1,000 +php: | + array( + 'zero' => 0, + 'simple' => 12, + 'one-thousand' => 1000, + 'negative one-thousand' => -1000 + ) +--- +test: Integers as Map Keys +brief: > + An integer can be used a dictionary key. +yaml: | + 1: one + 2: two + 3: three +php: | + array( + 1 => 'one', + 2 => 'two', + 3 => 'three' + ) +--- +test: Floats +dump_skip: true +brief: > + Floats are represented by numbers with decimals, + allowing for scientific notation, as well as + positive and negative infinity and "not a number." +yaml: | + a simple float: 2.00 + larger float: 1,000.09 + scientific notation: 1.00009e+3 +php: | + array( + 'a simple float' => 2.0, + 'larger float' => 1000.09, + 'scientific notation' => 1000.09 + ) +--- +test: Time +todo: true +brief: > + You can represent timestamps by using + ISO8601 format, or a variation which + allows spaces between the date, time and + time zone. +yaml: | + iso8601: 2001-12-14t21:59:43.10-05:00 + space seperated: 2001-12-14 21:59:43.10 -05:00 +php: | + array( + 'iso8601' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'space seperated' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ) + ) +--- +test: Date +todo: true +brief: > + A date can be represented by its year, + month and day in ISO8601 order. +yaml: | + 1976-07-31 +php: | + date( 1976, 7, 31 ) diff --git a/plugins/sfYaml/test/fixtures/index.yml b/plugins/sfYaml/test/fixtures/index.yml new file mode 100755 index 0000000..dea165f --- /dev/null +++ b/plugins/sfYaml/test/fixtures/index.yml @@ -0,0 +1,16 @@ +- sfComments +- sfCompact +- sfTests +- sfObjects +- sfMergeKey +- sfQuotes +- YtsAnchorAlias +- YtsBasicTests +- YtsBlockMapping +- YtsDocumentSeparator +- YtsErrorTests +- YtsFlowCollections +- YtsFoldedScalars +- YtsNullsAndEmpties +- YtsSpecificationExamples +- YtsTypeTransfers diff --git a/plugins/sfYaml/test/fixtures/sfComments.yml b/plugins/sfYaml/test/fixtures/sfComments.yml new file mode 100755 index 0000000..c02940a --- /dev/null +++ b/plugins/sfYaml/test/fixtures/sfComments.yml @@ -0,0 +1,51 @@ +--- %YAML:1.0 +test: Comments at the end of a line +brief: > + Comments at the end of a line +yaml: | + ex1: "foo # bar" + ex2: "foo # bar" # comment + ex3: 'foo # bar' # comment + ex4: foo # comment +php: | + array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo') +--- +test: Comments in the middle +brief: > + Comments in the middle +yaml: | + foo: + # some comment + # some comment + bar: foo + # some comment + # some comment +php: | + array('foo' => array('bar' => 'foo')) +--- +test: Comments on a hash line +brief: > + Comments on a hash line +yaml: | + foo: # a comment + foo: bar # a comment +php: | + array('foo' => array('foo' => 'bar')) +--- +test: 'Value starting with a #' +brief: > + 'Value starting with a #' +yaml: | + foo: '#bar' +php: | + array('foo' => '#bar') +--- +test: Document starting with a comment and a separator +brief: > + Commenting before document start is allowed +yaml: | + # document comment + --- + foo: bar # a comment +php: | + array('foo' => 'bar') diff --git a/plugins/sfYaml/test/fixtures/sfCompact.yml b/plugins/sfYaml/test/fixtures/sfCompact.yml new file mode 100755 index 0000000..6bb4b84 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/sfCompact.yml @@ -0,0 +1,53 @@ +--- %YAML:1.0 +test: Compact notation +brief: | + Compact notation for sets of mappings with single element +yaml: | + --- + # products purchased + - item : Super Hoop + - item : Basketball + quantity: 1 + - item: + name: Big Shoes + nick: Biggies + quantity: 1 +php: | + array ( + array ( + 'item' => 'Super Hoop', + ), + array ( + 'item' => 'Basketball', + 'quantity' => 1, + ), + array ( + 'item' => array( + 'name' => 'Big Shoes', + 'nick' => 'Biggies' + ), + 'quantity' => 1 + ) + ) +--- +test: Compact notation combined with inline notation +brief: | + Combinations of compact and inline notation are allowed +yaml: | + --- + items: + - { item: Super Hoop, quantity: 1 } + - [ Basketball, Big Shoes ] +php: | + array ( + 'items' => array ( + array ( + 'item' => 'Super Hoop', + 'quantity' => 1, + ), + array ( + 'Basketball', + 'Big Shoes' + ) + ) + ) diff --git a/plugins/sfYaml/test/fixtures/sfMergeKey.yml b/plugins/sfYaml/test/fixtures/sfMergeKey.yml new file mode 100755 index 0000000..3eec4f8 --- /dev/null +++ b/plugins/sfYaml/test/fixtures/sfMergeKey.yml @@ -0,0 +1,27 @@ +--- %YAML:1.0 +test: Simple In Place Substitution +brief: > + If you want to reuse an entire alias, only overwriting what is different + you can use a << in place substitution. This is not part of the official + YAML spec, but a widely implemented extension. See the following URL for + details: http://yaml.org/type/merge.html +yaml: | + foo: &foo + a: Steve + b: Clark + c: Brian + bar: &bar + <<: *foo + x: Oren + foo2: &foo2 + a: Ballmer + ding: &dong [ fi, fei, fo, fam] + check: + <<: + - *foo + - *dong + isit: tested + head: + <<: [ *foo , *dong , *foo2 ] +php: | + array('foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), 'bar' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Ballmer', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam')) diff --git a/plugins/sfYaml/test/fixtures/sfObjects.yml b/plugins/sfYaml/test/fixtures/sfObjects.yml new file mode 100755 index 0000000..454ceae --- /dev/null +++ b/plugins/sfYaml/test/fixtures/sfObjects.yml @@ -0,0 +1,11 @@ +--- %YAML:1.0 +test: Objects +brief: > + Comments at the end of a line +yaml: | + ex1: "foo # bar" + ex2: "foo # bar" # comment + ex3: 'foo # bar' # comment + ex4: foo # comment +php: | + array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo') diff --git a/plugins/sfYaml/test/fixtures/sfQuotes.yml b/plugins/sfYaml/test/fixtures/sfQuotes.yml new file mode 100755 index 0000000..741f1be --- /dev/null +++ b/plugins/sfYaml/test/fixtures/sfQuotes.yml @@ -0,0 +1,33 @@ +--- %YAML:1.0 +test: Some characters at the beginning of a string must be escaped +brief: > + Some characters at the beginning of a string must be escaped +yaml: | + foo: | bar +php: | + array('foo' => '| bar') +--- +test: A key can be a quoted string +brief: > + A key can be a quoted string +yaml: | + "foo1": bar + 'foo2': bar + "foo \" bar": bar + 'foo '' bar': bar + 'foo3: ': bar + "foo4: ": bar + foo5: { "foo \" bar: ": bar, 'foo '' bar: ': bar } +php: | + array( + 'foo1' => 'bar', + 'foo2' => 'bar', + 'foo " bar' => 'bar', + 'foo \' bar' => 'bar', + 'foo3: ' => 'bar', + 'foo4: ' => 'bar', + 'foo5' => array( + 'foo " bar: ' => 'bar', + 'foo \' bar: ' => 'bar', + ), + ) diff --git a/plugins/sfYaml/test/fixtures/sfTests.yml b/plugins/sfYaml/test/fixtures/sfTests.yml new file mode 100755 index 0000000..8eff31c --- /dev/null +++ b/plugins/sfYaml/test/fixtures/sfTests.yml @@ -0,0 +1,145 @@ +--- %YAML:1.0 +test: Multiple quoted string on one line +brief: > + Multiple quoted string on one line +yaml: | + stripped_title: { name: "foo bar", help: "bar foo" } +php: | + array('stripped_title' => array('name' => 'foo bar', 'help' => 'bar foo')) +--- +test: Empty sequence +yaml: | + foo: [ ] +php: | + array('foo' => array()) +--- +test: Empty value +yaml: | + foo: +php: | + array('foo' => null) +--- +test: Inline string parsing +brief: > + Inline string parsing +yaml: | + test: ['complex: string', 'another [string]'] +php: | + array('test' => array('complex: string', 'another [string]')) +--- +test: Boolean +brief: > + Boolean +yaml: | + - false + - - + - off + - no + - true + - + + - on + - yes + - null + - ~ + - 'false' + - '-' + - 'off' + - 'no' + - 'true' + - '+' + - 'on' + - 'yes' + - 'null' + - '~' +php: | + array( + false, + false, + false, + false, + true, + true, + true, + true, + null, + null, + 'false', + '-', + 'off', + 'no', + 'true', + '+', + 'on', + 'yes', + 'null', + '~', + ) +--- +test: Empty lines in folded blocks +brief: > + Empty lines in folded blocks +yaml: | + foo: + bar: | + foo + + + + bar +php: | + array('foo' => array('bar' => "foo\n\n\n \nbar\n")) +--- +test: IP addresses +brief: > + IP addresses +yaml: | + foo: 10.0.0.2 +php: | + array('foo' => '10.0.0.2') +--- +test: A sequence with an embedded mapping +brief: > + A sequence with an embedded mapping +yaml: | + - foo + - bar: { bar: foo } +php: | + array('foo', array('bar' => array('bar' => 'foo'))) +--- +test: A sequence with an unordered array +brief: > + A sequence with an unordered array +yaml: | + 1: foo + 0: bar +php: | + array(1 => 'foo', 0 => 'bar') +--- +test: Octal +brief: as in spec example 2.19, octal value is converted +yaml: | + foo: 0123 +php: | + array('foo' => 83) +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: "0123" +php: | + array('foo' => '0123') +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: '0123' +php: | + array('foo' => '0123') +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: | + 0123 +php: | + array('foo' => "0123\n") diff --git a/plugins/sfYaml/test/prove.php b/plugins/sfYaml/test/prove.php new file mode 100755 index 0000000..795a105 --- /dev/null +++ b/plugins/sfYaml/test/prove.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once(dirname(__FILE__).'/lime/lime.php'); + +$h = new lime_harness(array( + 'force_colors' => isset($argv) && in_array('--color', $argv), + 'verbose' => isset($argv) && in_array('--verbose', $argv), +)); +$h->base_dir = realpath(dirname(__FILE__)); + +foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__FILE__)), RecursiveIteratorIterator::LEAVES_ONLY) as $file) +{ + if (preg_match('/Test\.php$/', $file)) + { + $h->register($file->getRealPath()); + } +} + +exit($h->run() ? 0 : 1); diff --git a/plugins/sfYaml/test/sfYamlDumperTest.php b/plugins/sfYaml/test/sfYamlDumperTest.php new file mode 100755 index 0000000..0519cef --- /dev/null +++ b/plugins/sfYaml/test/sfYamlDumperTest.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once(dirname(__FILE__).'/lime/lime.php'); +require_once(dirname(__FILE__).'/../lib/sfYaml.php'); +require_once(dirname(__FILE__).'/../lib/sfYamlParser.php'); +require_once(dirname(__FILE__).'/../lib/sfYamlDumper.php'); + +sfYaml::setSpecVersion('1.1'); + +$t = new lime_test(152); + +$parser = new sfYamlParser(); +$dumper = new sfYamlDumper(); + +$path = dirname(__FILE__).'/fixtures'; +$files = $parser->parse(file_get_contents($path.'/index.yml')); +foreach ($files as $file) +{ + $t->diag($file); + + $yamls = file_get_contents($path.'/'.$file.'.yml'); + + // split YAMLs documents + foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) + { + if (!$yaml) + { + continue; + } + + $test = $parser->parse($yaml); + if (isset($test['dump_skip']) && $test['dump_skip']) + { + continue; + } + else if (isset($test['todo']) && $test['todo']) + { + $t->todo($test['test']); + } + else + { + $expected = eval('return '.trim($test['php']).';'); + + $t->is_deeply($parser->parse($dumper->dump($expected, 10)), $expected, $test['test']); + } + } +} + +// inline level +$array = array( + '' => 'bar', + 'foo' => '#bar', + 'foo\'bar' => array(), + 'bar' => array(1, 'foo'), + 'foobar' => array( + 'foo' => 'bar', + 'bar' => array(1, 'foo'), + 'foobar' => array( + 'foo' => 'bar', + 'bar' => array(1, 'foo'), + ), + ), +); + +$expected = <<is($dumper->dump($array, -10), $expected, '->dump() takes an inline level argument'); +$t->is($dumper->dump($array, 0), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 1), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 2), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 3), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 4), $expected, '->dump() takes an inline level argument'); +$t->is($dumper->dump($array, 10), $expected, '->dump() takes an inline level argument'); + +// objects +$t->diag('Objects support'); +class A +{ + public $a = 'foo'; +} +$a = array('foo' => new A(), 'bar' => 1); +$t->is($dumper->dump($a), '{ foo: !!php/object:O:1:"A":1:{s:1:"a";s:3:"foo";}, bar: 1 }', '->dump() is able to dump objects'); diff --git a/plugins/sfYaml/test/sfYamlInlineTest.php b/plugins/sfYaml/test/sfYamlInlineTest.php new file mode 100755 index 0000000..097fb26 --- /dev/null +++ b/plugins/sfYaml/test/sfYamlInlineTest.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once(dirname(__FILE__).'/lime/lime.php'); +require_once(dirname(__FILE__).'/../lib/sfYaml.php'); +require_once(dirname(__FILE__).'/../lib/sfYamlInline.php'); + +sfYaml::setSpecVersion('1.1'); + +$t = new lime_test(124); + +// ::load() +$t->diag('::load()'); + +$testsForLoad = array( + '' => '', + 'null' => null, + 'false' => false, + 'true' => true, + '12' => 12, + '"quoted string"' => 'quoted string', + "'quoted string'" => 'quoted string', + '12.30e+02' => 12.30e+02, + '0x4D2' => 0x4D2, + '02333' => 02333, + '.Inf' => -log(0), + '-.Inf' => log(0), + '123456789123456789' => '123456789123456789', + '"foo\r\nbar"' => "foo\r\nbar", + "'foo#bar'" => 'foo#bar', + "'foo # bar'" => 'foo # bar', + "'#cfcfcf'" => '#cfcfcf', + + '2007-10-30' => mktime(0, 0, 0, 10, 30, 2007), + '2007-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 2007), + '2007-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 2007), + + '"a \\"string\\" with \'quoted strings inside\'"' => 'a "string" with \'quoted strings inside\'', + "'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'', + + // sequences + // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon + '[foo, http://urls.are/no/mappings, false, null, 12]' => array('foo', 'http://urls.are/no/mappings', false, null, 12), + '[ foo , bar , false , null , 12 ]' => array('foo', 'bar', false, null, 12), + '[\'foo,bar\', \'foo bar\']' => array('foo,bar', 'foo bar'), + + // mappings + '{foo:bar,bar:foo,false:false,null:null,integer:12}' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12), + '{ foo : bar, bar : foo, false : false, null : null, integer : 12 }' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12), + '{foo: \'bar\', bar: \'foo: bar\'}' => array('foo' => 'bar', 'bar' => 'foo: bar'), + '{\'foo\': \'bar\', "bar": \'foo: bar\'}' => array('foo' => 'bar', 'bar' => 'foo: bar'), + '{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}' => array('foo\'' => 'bar', "bar\"" => 'foo: bar'), + '{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}' => array('foo: ' => 'bar', "bar: " => 'foo: bar'), + + // nested sequences and mappings + '[foo, [bar, foo]]' => array('foo', array('bar', 'foo')), + '[foo, {bar: foo}]' => array('foo', array('bar' => 'foo')), + '{ foo: {bar: foo} }' => array('foo' => array('bar' => 'foo')), + '{ foo: [bar, foo] }' => array('foo' => array('bar', 'foo')), + + '[ foo, [ bar, foo ] ]' => array('foo', array('bar', 'foo')), + + '[{ foo: {bar: foo} }]' => array(array('foo' => array('bar' => 'foo'))), + + '[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')), + + '[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]' => array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo'))), + + '[foo, bar: { foo: bar }]' => array('foo', '1' => array('bar' => array('foo' => 'bar'))), +); + +foreach ($testsForLoad as $yaml => $value) +{ + $t->is_deeply(sfYamlInline::load($yaml), $value, sprintf('::load() converts an inline YAML to a PHP structure (%s)', $yaml)); +} + +$testsForDump = array( + 'null' => null, + 'false' => false, + 'true' => true, + '12' => 12, + "'quoted string'" => 'quoted string', + '12.30e+02' => 12.30e+02, + '1234' => 0x4D2, + '1243' => 02333, + '.Inf' => -log(0), + '-.Inf' => log(0), + '"foo\r\nbar"' => "foo\r\nbar", + "'foo#bar'" => 'foo#bar', + "'foo # bar'" => 'foo # bar', + "'#cfcfcf'" => '#cfcfcf', + + "'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'', + + // sequences + '[foo, bar, false, null, 12]' => array('foo', 'bar', false, null, 12), + '[\'foo,bar\', \'foo bar\']' => array('foo,bar', 'foo bar'), + + // mappings + '{ foo: bar, bar: foo, \'false\': false, \'null\': null, integer: 12 }' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12), + '{ foo: bar, bar: \'foo: bar\' }' => array('foo' => 'bar', 'bar' => 'foo: bar'), + + // nested sequences and mappings + '[foo, [bar, foo]]' => array('foo', array('bar', 'foo')), + + '[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')), + + '{ foo: { bar: foo } }' => array('foo' => array('bar' => 'foo')), + + '[foo, { bar: foo }]' => array('foo', array('bar' => 'foo')), + + '[foo, { bar: foo, foo: [foo, { bar: foo }] }, [foo, { bar: foo }]]' => array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo'))), +); + +// ::dump() +$t->diag('::dump()'); +foreach ($testsForDump as $yaml => $value) +{ + $t->is(sfYamlInline::dump($value), $yaml, sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml)); +} + +foreach ($testsForLoad as $yaml => $value) +{ + if ($value == 1230) + { + continue; + } + + $t->is_deeply(sfYamlInline::load(sfYamlInline::dump($value)), $value, 'check consistency'); +} + +foreach ($testsForDump as $yaml => $value) +{ + if ($value == 1230) + { + continue; + } + + $t->is_deeply(sfYamlInline::load(sfYamlInline::dump($value)), $value, 'check consistency'); +} diff --git a/plugins/sfYaml/test/sfYamlParserTest.php b/plugins/sfYaml/test/sfYamlParserTest.php new file mode 100755 index 0000000..e17f023 --- /dev/null +++ b/plugins/sfYaml/test/sfYamlParserTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once(dirname(__FILE__).'/lime/lime.php'); +require_once(dirname(__FILE__).'/../lib/sfYaml.php'); +require_once(dirname(__FILE__).'/../lib/sfYamlParser.php'); + +sfYaml::setSpecVersion('1.1'); + +$t = new lime_test(153); + +$parser = new sfYamlParser(); + +$path = dirname(__FILE__).'/fixtures'; +$files = $parser->parse(file_get_contents($path.'/index.yml')); +foreach ($files as $file) +{ + $t->diag($file); + + $yamls = file_get_contents($path.'/'.$file.'.yml'); + + // split YAMLs documents + foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) + { + if (!$yaml) + { + continue; + } + + $test = $parser->parse($yaml); + if (isset($test['todo']) && $test['todo']) + { + $t->todo($test['test']); + } + else + { + $expected = var_export(eval('return '.trim($test['php']).';'), true); + + $t->is(var_export($parser->parse($test['yaml']), true), $expected, $test['test']); + } + } +} + +// test tabs in YAML +$yamls = array( + "foo:\n bar", + "foo:\n bar", + "foo:\n bar", + "foo:\n bar", +); + +foreach ($yamls as $yaml) +{ + try + { + $content = $parser->parse($yaml); + $t->fail('YAML files must not contain tabs'); + } + catch (InvalidArgumentException $e) + { + $t->pass('YAML files must not contain tabs'); + } +} + +$yaml = <<is('foo', $parser->parse($yaml)); + +// objects +$t->diag('Objects support'); +class A +{ + public $a = 'foo'; +} +$a = array('foo' => new A(), 'bar' => 1); +$t->is($parser->parse(<<parse() is able to dump objects'); diff --git a/project/apps/back_end/Config.class.php b/project/apps/back_end/Config.class.php new file mode 100644 index 0000000..8b068bb --- /dev/null +++ b/project/apps/back_end/Config.class.php @@ -0,0 +1,27 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package projects + * @subpackage back_end + */ +class Config extends \project\config\Config { + + /** + * Function - Boot Strap + */ + public static function bootStrap(){ + /* + Put any boot strap functions in here... They will get called prior to the routing loading any classes + \nano\core\cache\Memcached::getInstance()->setKeyPrefix('back_end'); + */ + \nano\core\i18n\I18n::getInstance()->setLanguage('en_GB'); + } + +} \ No newline at end of file diff --git a/project/apps/back_end/Routing.class.php b/project/apps/back_end/Routing.class.php new file mode 100644 index 0000000..fc48552 --- /dev/null +++ b/project/apps/back_end/Routing.class.php @@ -0,0 +1,70 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package projects + * @subpackage back_end + */ +class Routing { + + public static $routing = array( + 'index_page' => array + ( + 'url' => '[/]?', + 'url_for' => '/', + 'class' => 'project\apps\back_end\pages\index\Index' + ), + 'login_page' => array + ( + 'url' => '/login(?:/(?[^/]+))?', + 'class' => 'project\apps\back_end\pages\login\Login' + ), + 'logout_page' => array + ( + 'url' => '/logout', + 'class' => 'project\apps\back_end\pages\logout\Logout' + ), + 'list_view_page' => array + ( + 'url' => '/:database/:model/list', + 'class' => 'project\apps\back_end\pages\list_view\ListView' + ), + 'view_page' => array + ( + 'url' => '/:database/:model/view', + 'class' => 'project\apps\back_end\widgets\view\View' + ), + 'edit_page' => array + ( + 'url' => '/:database/:model/edit', + 'class' => 'project\apps\back_end\pages\edit\Edit' + ), + 'add_page' => array + ( + 'url' => '/:database/:model/add', + 'class' => 'project\apps\back_end\pages\add\Add', + ), + 'model_export_page' => array + ( + 'url' => '/:database/:model/export', + 'class' => 'project\apps\back_end\pages\export\ModelExport' + ), + 'model_export_as_type_page' => array + ( + 'url' => '/:database/:model/export-as-(?csv)', + 'class' => 'project\apps\back_end\widgets\export\ModelExport' + ), + 'model_import_page' => array + ( + 'url' => '/:database/:model/import', + 'class' => 'project\apps\back_end\pages\import\ModelImport' + ), + ); + +} \ No newline at end of file diff --git a/project/apps/back_end/forms/form.twig b/project/apps/back_end/forms/form.twig new file mode 100644 index 0000000..e5cda6e --- /dev/null +++ b/project/apps/back_end/forms/form.twig @@ -0,0 +1,45 @@ +{% macro buildAdminForm(params) %} + {% if params.input.inputType == 'text' %} + {{ _self.adminInputText(params.input) }} + {% elseif params.input.inputType == 'textarea' %} + {{ _self.adminInputTextArea(params.input) }} + {% elseif params.input.inputType == 'file' %} + {{ _self.adminInputFile(params.input) }} + {% elseif params.input.inputType == 'radio' %} + {{ _self.adminInputRadio(params.input) }} + {% endif %} +{% endmacro %} + +{% macro adminLabel(params) %} +
    {{ params.value }}:
    +{% endmacro %} + +{% macro adminButton(params) %} + +{% endmacro %} + +{% macro adminInputText(params) %} +
    + +
    +{% endmacro %} + +{% macro adminInputTextArea(params) %} +
    + +
    +{% endmacro %} + +{% macro adminInputFile(params) %} +
    + +
    +{% endmacro %} + +{% macro adminInputRadio(params) %} +
    + {% for key, value in params.inputValue %} +   + {% endfor %} +
    +{% endmacro %} \ No newline at end of file diff --git a/project/apps/back_end/layouts/layout.twig b/project/apps/back_end/layouts/layout.twig new file mode 100644 index 0000000..06ac8a1 --- /dev/null +++ b/project/apps/back_end/layouts/layout.twig @@ -0,0 +1,29 @@ +{% block header %} + {{ header }} +{% endblock header %} + + {% block body %} + +
    + {% endblock body %} + + {% block logo %} + + {% endblock logo %} + + {% block content_container %} +
     
    + {% endblock content_container %} + + {% block end_body %} +
    + {% endblock %} + + {% block log_bar %} + {{ 'log' | inc }} + {% endblock log_bar %} + +{% block footer %} + + +{% endblock footer %} \ No newline at end of file diff --git a/project/apps/back_end/pages/add/Add.class.php b/project/apps/back_end/pages/add/Add.class.php new file mode 100644 index 0000000..f374e4f --- /dev/null +++ b/project/apps/back_end/pages/add/Add.class.php @@ -0,0 +1,36 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage add + */ +class Add extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + */ + public function execute(\nano\core\routing\Routing $routing){ + $this->headerWidget->setTitle(i18n('admin.pagetitle.add','Admin - Add New Record')); + + $menu = new \project\apps\back_end\widgets\menu\Menu(); + $this->menu = $menu->getRenderedWidget(); + + if($routing->getRequest()->getOnceOnlyCookie('add-form-record-preview')!==null){ + $this->previewUrl = '/ajax/'.$_GET['database'].'/'.$_GET['model'].'/view?ids='.$routing->getRequest()->getOnceOnlyCookie('add-form-record-preview'); + } + + $add = new \project\apps\back_end\widgets\add\Add(); + $this->add = $add->getRenderedWidget(); + + return 'project/apps/back_end/pages/add/views/add.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/pages/add/views/add.twig b/project/apps/back_end/pages/add/views/add.twig new file mode 100644 index 0000000..7ebc182 --- /dev/null +++ b/project/apps/back_end/pages/add/views/add.twig @@ -0,0 +1,25 @@ +{% extends "project/apps/back_end/layouts/layout.twig" %} + +{% block body %} + {% if previewUrl %} + + {% else %} + + {% endif %} +
    +{% endblock %} + +{% block content_container %} +
    + + + + + +
    + {{ menu }} + + {{ add }} +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/apps/back_end/pages/edit/Edit.class.php b/project/apps/back_end/pages/edit/Edit.class.php new file mode 100644 index 0000000..7632dff --- /dev/null +++ b/project/apps/back_end/pages/edit/Edit.class.php @@ -0,0 +1,36 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage edit + */ +class Edit extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + */ + public function execute(\nano\core\routing\Routing $routing){ + $this->headerWidget->setTitle(i18n('admin.pagetitle.edit','Admin - Edit Existing Record')); + + $menu = new \project\apps\back_end\widgets\menu\Menu(); + $this->menu = $menu->getRenderedWidget(); + + if($routing->getRequest()->getOnceOnlyCookie('edit-form-record-preview')!==null){ + $this->previewUrl = '/ajax/'.$_GET['database'].'/'.$_GET['model'].'/view?ids='.$_GET['ids']; + } + + $edit = new \project\apps\back_end\widgets\edit\Edit(); + $this->edit = $edit->getRenderedWidget(); + + return 'project/apps/back_end/pages/edit/views/edit.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/pages/edit/views/edit.twig b/project/apps/back_end/pages/edit/views/edit.twig new file mode 100644 index 0000000..e8cc928 --- /dev/null +++ b/project/apps/back_end/pages/edit/views/edit.twig @@ -0,0 +1,25 @@ +{% extends "project/apps/back_end/layouts/layout.twig" %} + +{% block body %} + {% if previewUrl %} + + {% else %} + + {% endif %} +
    +{% endblock %} + +{% block content_container %} +
    + + + + + +
    + {{ menu }} + + {{ edit }} +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/apps/back_end/pages/export/ModelExport.class.php b/project/apps/back_end/pages/export/ModelExport.class.php new file mode 100644 index 0000000..760b575 --- /dev/null +++ b/project/apps/back_end/pages/export/ModelExport.class.php @@ -0,0 +1,33 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage export + */ +class ModelExport extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @return mixed + */ + public function execute(\nano\core\routing\Routing $routing){ + $this->headerWidget->setTitle(i18n('admin.pagetitle.model-export','Admin - Export')); + + $menu = new \project\apps\back_end\widgets\menu\Menu(); + $this->menu = $menu->getRenderedWidget(); + + $modelExport = new \project\apps\back_end\widgets\export\ModelExport(); + $this->modelExport = $modelExport->getRenderedWidget(); + + return 'project/apps/back_end/pages/export/views/model_export.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/pages/export/views/model_export.twig b/project/apps/back_end/pages/export/views/model_export.twig new file mode 100644 index 0000000..7a5b1e4 --- /dev/null +++ b/project/apps/back_end/pages/export/views/model_export.twig @@ -0,0 +1,16 @@ +{% extends "project/apps/back_end/layouts/layout.twig" %} + +{% block content_container %} +
    + + + + + +
    + {{ menu }} + + {{ modelExport }} +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/apps/back_end/pages/import/ModelImport.class.php b/project/apps/back_end/pages/import/ModelImport.class.php new file mode 100644 index 0000000..b420980 --- /dev/null +++ b/project/apps/back_end/pages/import/ModelImport.class.php @@ -0,0 +1,33 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage import + */ +class ModelImport extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @return mixed + */ + public function execute(\nano\core\routing\Routing $routing){ + $this->headerWidget->setTitle(i18n('admin.pagetitle.model-import','Admin - Import')); + + $menu = new \project\apps\back_end\widgets\menu\Menu(); + $this->menu = $menu->getRenderedWidget(); + + $modelImport = new \project\apps\back_end\widgets\import\ModelImport(); + $this->modelImport = $modelImport->getRenderedWidget(); + + return 'project/apps/back_end/pages/import/views/model_import.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/pages/import/views/model_import.twig b/project/apps/back_end/pages/import/views/model_import.twig new file mode 100644 index 0000000..3d088c4 --- /dev/null +++ b/project/apps/back_end/pages/import/views/model_import.twig @@ -0,0 +1,16 @@ +{% extends "project/apps/back_end/layouts/layout.twig" %} + +{% block content_container %} +
    + + + + + +
    + {{ menu }} + + {{ modelImport }} +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/apps/back_end/pages/index/Index.class.php b/project/apps/back_end/pages/index/Index.class.php new file mode 100644 index 0000000..7387f37 --- /dev/null +++ b/project/apps/back_end/pages/index/Index.class.php @@ -0,0 +1,34 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage index + */ +class Index extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @return mixed + */ + public function execute(\nano\core\routing\Routing $routing){ + $this->headerWidget->setTitle(i18n('admin.pagetitle.home','Admin - Home Page')); + + $menu = new \project\apps\back_end\widgets\menu\Menu(); + $this->menu = $menu->getRenderedWidget(); + + $blockMenu = new \project\apps\back_end\widgets\menu\Menu(); + $blockMenu->setListType('blocks'); + $this->blockMenu = $blockMenu->getRenderedWidget(); + + return 'project/apps/back_end/pages/index/views/index.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/pages/index/views/index.twig b/project/apps/back_end/pages/index/views/index.twig new file mode 100644 index 0000000..21938cf --- /dev/null +++ b/project/apps/back_end/pages/index/views/index.twig @@ -0,0 +1,16 @@ +{% extends "project/apps/back_end/layouts/layout.twig" %} + +{% block content_container %} +
    + + + + + +
    + {{ menu }} + + {{ blockMenu }} +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/apps/back_end/pages/list_view/ListView.class.php b/project/apps/back_end/pages/list_view/ListView.class.php new file mode 100644 index 0000000..e4e0fdf --- /dev/null +++ b/project/apps/back_end/pages/list_view/ListView.class.php @@ -0,0 +1,39 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage list_view + */ +class ListView extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + */ + public function execute(\nano\core\routing\Routing $routing){ + $this->headerWidget->setTitle(i18n('admin.pagetitle.list','Admin - List Records')); + + $menu = new \project\apps\back_end\widgets\menu\Menu(); + $this->menu = $menu->getRenderedWidget(); + + $filter = new \project\apps\back_end\widgets\filter\Filter(); + $this->filter = $filter->getRenderedWidget(); + + $displayList = new \project\apps\back_end\widgets\display_list\DisplayList(); + $this->displayList = $displayList->getRenderedWidget(); + + if($routing->getRequest()->getOnceOnlyCookie('edit-form-record-deleted')!==null){ + $this->successMessage = 'Record has been successfully deleted'; + } + + return 'project/apps/back_end/pages/list_view/views/list_view.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/pages/list_view/views/list_view.twig b/project/apps/back_end/pages/list_view/views/list_view.twig new file mode 100644 index 0000000..0242718 --- /dev/null +++ b/project/apps/back_end/pages/list_view/views/list_view.twig @@ -0,0 +1,22 @@ +{% extends "project/apps/back_end/layouts/layout.twig" %} + +{% block content_container %} +
    + + + + + +
    + {{ menu }} + +
    + {{ filter }} + {% if successMessage %} +
    {{ successMessage }}
    + {% endif %} + {{ displayList }} +
    +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/apps/back_end/pages/login/Login.class.php b/project/apps/back_end/pages/login/Login.class.php new file mode 100644 index 0000000..c23b01a --- /dev/null +++ b/project/apps/back_end/pages/login/Login.class.php @@ -0,0 +1,58 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage login + */ +class Login extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Pre Load + * @param \nano\core\routing\Routing $routing Routing + */ + protected function preLoad(\nano\core\routing\Routing $routing){ + //over-ride parent + } + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + */ + public function execute(\nano\core\routing\Routing $routing){ + + $this->headerWidget->setTitle(i18n('admin.pagetitle.login','Admin - Login Page')); + + if(isset($_GET['reason'])){ + switch($_GET['reason']){ + case 'super-admin-required': + $this->errorMessage = i18n('admin.login.error.super-admin-required','Super Admin Permissions Required'); + break; + case 'admin-required': + $this->errorMessage = i18n('admin.login.error.admin-required','Admin Permissions Required'); + break; + } + } + + if(\project\session\Session::isAdmin()){ + $routing->getResponse()->pageRedirect('/'); + } + + if(!empty($_POST)){ + if(\project\session\Session::login($_POST['username'],$_POST['password'])){ + $routing->getResponse()->pageRedirect('/'); + } else { + $this->errorMessage = i18n('admin.login.error.incorrect-username-or-password','Incorrect Username or Password'); + } + } + + return 'project/apps/back_end/pages/login/views/login.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/pages/login/views/login.twig b/project/apps/back_end/pages/login/views/login.twig new file mode 100644 index 0000000..91c95d9 --- /dev/null +++ b/project/apps/back_end/pages/login/views/login.twig @@ -0,0 +1,79 @@ +{% extends "project/apps/back_end/layouts/layout.twig" %} + +{% block logo %} + +{% endblock logo %} + +{% block content_container %} + + + +
    + + + + + + + +
    +
    +
    +888b    888        d8888 888b    888  .d88888b.  8888888b.  888    888 8888888b.  
    +8888b   888       d88888 8888b   888 d88P" "Y88b 888   Y88b 888    888 888   Y88b 
    +88888b  888      d88P888 88888b  888 888     888 888    888 888    888 888    888 
    +888Y88b 888     d88P 888 888Y88b 888 888     888 888   d88P 8888888888 888   d88P 
    +888 Y88b888    d88P  888 888 Y88b888 888     888 8888888P"  888    888 8888888P"  
    +888  Y88888   d88P   888 888  Y88888 888     888 888        888    888 888        
    +888   Y8888  d8888888888 888   Y8888 Y88b. .d88P 888        888    888 888        
    +888    Y888 d88P     888 888    Y888  "Y88888P"  888        888    888 888        
    +
    +
    +
    +
    + + + + +
    +
    + + {% if errorMessage %} + + + + {% endif %} + + + + + + + + + +
    +
    {{ errorMessage }}
    +
    +
    Username:
    +
    + +
    +
    +
    Password:
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/apps/back_end/pages/logout/Logout.class.php b/project/apps/back_end/pages/logout/Logout.class.php new file mode 100644 index 0000000..21b8c5b --- /dev/null +++ b/project/apps/back_end/pages/logout/Logout.class.php @@ -0,0 +1,50 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package pages + * @subpackage logout + */ +class Logout extends \project\apps\back_end\templates\PageTemplate { + + /** + * Function - Pre Load + * @param \nano\core\routing\Routing $routing Routing + */ + protected function preLoad(\nano\core\routing\Routing $routing){ + //over-ride parent + } + + /** + * Function - Pre Execute + * @param \nano\core\routing\Routing $routing Routing + */ + protected function preExecute(\nano\core\routing\Routing $routing){ + //over-ride parent + } + + /** + * Function - Post Execute + * @param \nano\core\routing\Routing $routing Routing + */ + protected function postExecute(\nano\core\routing\Routing $routing){ + //over-ride parent + } + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @return mixed + */ + public function execute(\nano\core\routing\Routing $routing){ + \project\session\Session::logout(); + return null; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/templates/PageTemplate.class.php b/project/apps/back_end/templates/PageTemplate.class.php new file mode 100644 index 0000000..f2c1738 --- /dev/null +++ b/project/apps/back_end/templates/PageTemplate.class.php @@ -0,0 +1,52 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package back_end + * @subpackage templates + */ +class PageTemplate extends \nano\core\page\Page { + + protected $headerWidget = null; + + /** + * Function - Pre Load + * @param \nano\core\routing\Routing $routing Routing + */ + protected function preLoad(\nano\core\routing\Routing $routing){ + if(!\project\session\Session::isAdmin()){ + $routing->getResponse()->pageRedirect('/login'); + } + } + + /** + * Function - Pre Execute + * @param \nano\core\routing\Routing $routing Routing + */ + protected function preExecute(\nano\core\routing\Routing $routing) { + $this->headerWidget = new \project\apps\back_end\widgets\header\Header(); + $this->headerWidget->addStyleSheet('/css/globals/admin.css'); + $this->headerWidget->addStyleSheet('/css/globals/default.css'); + $this->headerWidget->addStyleSheet('/css/globals/jquery/plugins/ui/'.\nano\core\config\Config::get('jquery_theme').'/jquery-ui-1.8.5.custom.css'); + $this->headerWidget->addStyleSheet('/css/globals/jquery/plugins/colorbox/colorbox-default.css'); + $this->headerWidget->addJavaScript('/js/globals/jquery.min.js'); + $this->headerWidget->addJavaScript('/js/globals/jquery-ui.min.js'); + $this->headerWidget->addJavaScript('/js/globals/jquery.colorbox-min.js'); + $this->headerWidget->addJavaScript('/js/globals/core.js'); + $this->headerWidget->addJavaScript('/js/globals/admin.js'); + } + + /** + * Function - Post Execute + * @param \nano\core\routing\Routing $routing Routing + */ + protected function postExecute(\nano\core\routing\Routing $routing) { + $this->header = $this->headerWidget->getRenderedWidget(); + } +} \ No newline at end of file diff --git a/project/apps/back_end/templates/WidgetTemplate.class.php b/project/apps/back_end/templates/WidgetTemplate.class.php new file mode 100644 index 0000000..1340768 --- /dev/null +++ b/project/apps/back_end/templates/WidgetTemplate.class.php @@ -0,0 +1,27 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package back_end + * @subpackage templates + */ +class WidgetTemplate extends \nano\core\widget\Widget { + + /** + * Function - Pre Load + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + protected function preLoad(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + if(!\project\session\Session::isAdmin()){ + $routing->getResponse()->pageRedirect('/login'); + } + } + +} \ No newline at end of file diff --git a/project/apps/back_end/tests/unit/conf/bootstrap.php b/project/apps/back_end/tests/unit/conf/bootstrap.php new file mode 100644 index 0000000..1ff366b --- /dev/null +++ b/project/apps/back_end/tests/unit/conf/bootstrap.php @@ -0,0 +1,2 @@ + + + + project/apps/back_end/tests/unit/lib + + + \ No newline at end of file diff --git a/project/apps/back_end/widgets/add/Add.class.php b/project/apps/back_end/widgets/add/Add.class.php new file mode 100644 index 0000000..76c579a --- /dev/null +++ b/project/apps/back_end/widgets/add/Add.class.php @@ -0,0 +1,165 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage add + */ +class Add extends \project\apps\back_end\templates\WidgetTemplate { + + protected $errorStack = array(); + protected $model = null; + + /** + * Function - Post Model Save + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + protected function postModelSave(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + $routing->getResponse()->pageRedirect(); + } + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->isSaved = ($routing->getRequest()->getOnceOnlyCookie('add-form-saved')===null)? false : true; + + if(file_exists($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/'.$_GET['database'].'/'.$_GET['model'].'Table.class.php')){ + $modelTable = \nano\core\db\ORM::getInstance()->getTable($_GET['model'],$_GET['database']); + $fieldInformation = $modelTable->getFieldInformation(); + $fieldNames = array_keys($fieldInformation); + + if(!empty($_POST)){ + $modelClassName = 'project\db\om\\'.$_GET['database'].'\\'.$_GET['model']; + $this->model = new $modelClassName(); + //this is a post, so try and save + foreach($_POST[$modelTable->getModelName()] as $key => $value){ + try{ + $this->model->$fieldInformation[$key]['set_function']($value); + } catch (\Exception $e) { + $this->errorStack[$key] = i18n('admin.add.error.could-not-set-value','Could not set value for \'{key}\' as it failed validation',array('{key}' => $key)); + } + } + if(empty($this->errorStack)){ + try{ + $this->model->save(); + $ids = array(); + foreach($fieldNames as $fieldName){ + if($fieldInformation[$fieldName]['mysql_key'] == 'PRI'){ + $ids[$fieldName] = $this->model->getValueFromKey($fieldName); + } + } + $routing->getResponse()->setOnceOnlyCookie('add-form-saved',''); + if($_POST['submit-form']=='save-preview'){ + $routing->getResponse()->setOnceOnlyCookie('add-form-record-preview',rawurlencode(base64_encode(json_encode($ids)))); + } + $this->postModelSave($routing,$pageInstance); + } catch (\Exception $e) { + //just log + $this->errorStack[] = i18n('admin.add.error.unable-to-save','Unable to save...'); + } + } + } + + $this->addForm = array(); + + foreach($fieldInformation as $key => $details){ + if($fieldInformation[$key]['mysql_extra']!='auto_increment'){ + $inputType = $this->getType($details); + //check to see if the type can be added via manual input... + if($inputType!==null){ + $inputValue = isset($_POST[$modelTable->getModelName()][$key])? $_POST[$modelTable->getModelName()][$key] : $fieldInformation[$key]['mysql_default']; + $this->addForm[$key] = array( + 'label' => array( + 'value' => i18n('admin.label-for-field.'.$_GET['database'].'.'.$_GET['model'].'.'.$key,$this->getLabel($key,$details)) + ), + 'input' => array( + 'inputType' => $inputType, + 'inputName' => $modelTable->getModelName().'['.$key.']', + 'inputValue' => $inputValue + ) + ); + } + } + } + + $this->addButton = array( + 'buttonValue' => 'save', + 'buttonLabel' => i18n('admin.add.save-button-value','Save') + ); + + $this->addAndPreviewButton = array( + 'buttonValue' => 'save-preview', + 'buttonLabel' => i18n('admin.add.save-and-preview-button-value','Save & Preview') + ); + + } else { + $this->errorStack[] = i18n('admin.add.error.unable-find-data-model','Unable to find data model...'); + } + + $this->addErrors = $this->errorStack; + $this->databaseName = $_GET['database']; + $this->modelName = $_GET['model']; + + return 'project/apps/back_end/widgets/add/views/add.twig'; + } + + /** + * Function - Get Label + * @param mixed $key Key + */ + protected function getLabel($key){ + $key = preg_replace('/(.*?)\.(.+)$/','$2',$key); + $key = preg_replace('/([a-z]{1})([A-Z]{1})/','$1 $2',$key); + $key = preg_replace('/\_([a-z]{1})/ie',"' '.strtoupper('\\1')",$key); + return ucfirst($key); + } + + /** + * Function - Get Type + * @param mixed $details Details + * @return mixed + */ + protected function getType($details){ + if(preg_match('/^([a-z]+)/',$details['mysql_type'],$matches)){ + switch($matches[1]){ + case 'int': + return 'text'; + break; + case 'varchar': + return 'text'; + break; + case 'char': + return 'text'; + break; + case 'text': + return 'textarea'; + break; + case 'date': + return 'text'; + break; + case 'datetime': + return 'text'; + break; + case 'tinyint': + return 'text'; + break; + case 'smallint': + return 'text'; + break; + } + } + return null; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/add/views/add.twig b/project/apps/back_end/widgets/add/views/add.twig new file mode 100644 index 0000000..f70c701 --- /dev/null +++ b/project/apps/back_end/widgets/add/views/add.twig @@ -0,0 +1,55 @@ +{% import 'project/apps/back_end/forms/form.twig' as form %} + +{% block outer_script %} + +{% endblock outer_script %} + +{% block form_construct %} +
    +{% endblock form_construct %} +
    +

    {{ 'admin.add-record' | i18n('Add Record') }}

    + + + {% if isSaved == true %} +
    {{ 'admin.add.record.added' | i18n('Record Added') }}
    + {% endif %} + + {% for key, value in addErrors %} +
    {{ value }}
    + {% endfor %} + + {% if addForm %} + {% for key, params in addForm %} + {% block row %} + + + + + {% endblock row %} + {% endfor %} + {% block buttons %} + + + + + {% endblock buttons %} + {% endif %} +
    {{ form.adminLabel(params.label) }}{{ form.buildAdminForm(params) }}
     {{ form.adminButton(addButton) }}{{ form.adminButton(addAndPreviewButton) }}
    +
    +
    \ No newline at end of file diff --git a/project/apps/back_end/widgets/delete/Delete.class.php b/project/apps/back_end/widgets/delete/Delete.class.php new file mode 100644 index 0000000..cac2395 --- /dev/null +++ b/project/apps/back_end/widgets/delete/Delete.class.php @@ -0,0 +1,32 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage delete + */ +class Delete extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + * @return mixed + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->deleteButton = array( + 'buttonValue' => 'delete', + 'buttonLabel' => i18n('admin.delete.delete-button-value','Delete Record') + ); + + return 'project/apps/back_end/widgets/delete/views/delete.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/delete/views/delete.twig b/project/apps/back_end/widgets/delete/views/delete.twig new file mode 100644 index 0000000..5622b0b --- /dev/null +++ b/project/apps/back_end/widgets/delete/views/delete.twig @@ -0,0 +1,3 @@ +{% import 'project/apps/back_end/forms/form.twig' as form %} + +{{ form.adminButton(deleteButton) }} \ No newline at end of file diff --git a/project/apps/back_end/widgets/display_list/DisplayList.class.php b/project/apps/back_end/widgets/display_list/DisplayList.class.php new file mode 100644 index 0000000..3e10fee --- /dev/null +++ b/project/apps/back_end/widgets/display_list/DisplayList.class.php @@ -0,0 +1,140 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage display_list + */ +class DisplayList extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->displayListErrors = array(); + $this->limit = 20; + $this->showNext = false; + $this->showPrevious = false; + $this->previousPage = null; + $this->nextPage = null; + $this->currentPage = isset($_POST['page'])? $_POST['page'] : 1; + $this->orderByField = isset($_POST['orderby']['field'])? $_POST['orderby']['field'] : ''; + $this->orderByOrder = isset($_POST['orderby']['order'])? $_POST['orderby']['order'] : ''; + if($this->currentPage > 1){ + $this->showPrevious = true; + $this->previousPage = $this->currentPage - 1; + } + + if(file_exists($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/'.$_GET['database'].'/'.$_GET['model'].'Table.class.php')){ + $modelTable = \nano\core\db\ORM::getInstance()->getTable($_GET['model'],$_GET['database']); + $fieldInformation = $modelTable->getFieldInformation(); + + $fieldNames = array_keys($fieldInformation); + foreach($fieldNames as $fieldName){ + $this->fieldNames[$fieldName] = i18n('admin.label-for-field.'.$_GET['database'].'.'.$_GET['model'].'.'.$fieldName,$this->getLabel($fieldName)); + } + + $orderBy = ''; + if($this->orderByField!=''){ + if(in_array($this->orderByField,$fieldNames)){ + if($this->orderByOrder=='asc'||$this->orderByOrder=='desc'){ + $orderBy = 'ORDER BY `'.$this->orderByField.'` '.strtoupper($this->orderByOrder).' '; + } + } + } + + $this->displayItems = array(); + + $results = array(); + try{ + $query = new \nano\core\db\core\SelectQuery(); + $query->from($modelTable->getTableName())->where($this->getWhere($fieldNames,$fieldInformation).' '.$orderBy.'LIMIT '.($this->limit+1).' OFFSET '.($this->limit*($this->currentPage-1))); + + $results = $modelTable->doSelect($query); + + if(count($results)>$this->limit){ + $this->showNext = true; + $this->nextPage = $this->currentPage + 1; + unset($results[$this->limit]); + } + + } catch (\Exception $e) { + //just log + } + + foreach($results as $result){ + $items = array(); + $ids = array(); + foreach($fieldNames as $fieldName){ + if($fieldInformation[$fieldName]['is_foreign_reference']){ + $items[$fieldName] = $result[$modelTable->getModelName()]->getValueFromKey($fieldInformation[$fieldName]['use_model']); + } else { + $items[$fieldName] = $result[$modelTable->getModelName()]->getValueFromKey($fieldName); + //$items[$fieldName] = $result[$modelTable->getModelName()]->{$fieldInformation[$fieldName]['get_function']}(); + } + if($fieldInformation[$fieldName]['mysql_key'] == 'PRI'){ + $ids[$fieldName] = $result[$modelTable->getModelName()]->getValueFromKey($fieldName); + } + } + $items['___pks'] = rawurlencode(base64_encode(json_encode($ids))); + $this->displayItems[] = $items; + } + + $this->databaseName = $_GET['database']; + $this->modelName = $_GET['model']; + + + } else { + $this->displayListErrors = i18n('admin.list.error.model-table-not-found','Model Table Not Found...'); + } + + + return 'project/apps/back_end/widgets/display_list/views/display_list.twig'; + } + + /** + * Function - Get Where + * @param mixed $fieldNames Field Names + * @param mixed $fieldInformation Field Information + */ + protected function getWhere($fieldNames,$fieldInformation){ + $filterArray = isset($_POST['filter'])? $_POST['filter'] : array(); + $where = ''; + if(!empty($filterArray)){ + foreach($filterArray as $key => $value){ + if((string)$value!=''){ + if(in_array($key,$fieldNames)){ + if(preg_match('/^(text|varchar)/',$fieldInformation[$key]['mysql_type'])){ + $where .= '`'.$key.'` LIKE "%'.addslashes($value).'%" AND '; + } else { + $where .= '`'.$key.'` = "'.addslashes($value).'" AND '; + } + } + } + } + $where = preg_replace('/\s{1}AND\s{1}$/','',$where); + } + return ($where=='')? '1' : $where; + } + + /** + * Function - Get Label + * @param mixed $key Key + */ + protected function getLabel($key){ + $key = preg_replace('/(.*?)\.(.+)$/','$2',$key); + $key = preg_replace('/([a-z]{1})([A-Z]{1})/','$1 $2',$key); + $key = preg_replace('/\_([a-z]{1})/ie',"' '.strtoupper('\\1')",$key); + return ucfirst($key); + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/display_list/views/display_list.twig b/project/apps/back_end/widgets/display_list/views/display_list.twig new file mode 100644 index 0000000..4975739 --- /dev/null +++ b/project/apps/back_end/widgets/display_list/views/display_list.twig @@ -0,0 +1,98 @@ +{% block outer_script %} + +{% endblock outer_script %} + +
    +

    {{ 'admin.list-view' | i18n('List View') }}

    + + + {% block main_table_construct %} + + {% endblock main_table_construct %} + {% if displayItems | length == 0 %} + + + + {% else %} + + {% for key, value in fieldNames %} + + {% endfor %} + {% block actions_title %} + + {% endblock actions_title %} + + {% for key, displayItem in displayItems %} + {% if (loop.index % 2) == 0 %} + + {% else %} + + {% endif %} + {% for key, value in displayItem %} + {% if fieldNames[key] %} + {% block row %} + {% if loop.first %} + + {% else %} + + {% endif %} + {% endblock row %} + {% endif %} + {% endfor %} + + + {% endfor %} + {% endif %} +
    {{ 'admin.display-list.no-results-found' | i18n('No Results Found') }}
    + {% if loop.first %} +
    + {% endif %} + {{ value }}{% if key == orderByField %}{% if orderByOrder == 'asc' %}
    {% else %}
    {% endif %}{% endif %} + {% if loop.first %} +
    + {% endif %} +
    {{ 'admin.display-list.actions' | i18n('Actions') }}
    {{ value | strcap(40) }}
    {{ value | strcap(40) }}
    + {% block actions %} + + {% endblock actions %} +
    + {% if displayItems | length != 0 %} +
    + + {% if showPrevious == true %} + < {{ 'admin.display-list.previous' | i18n('Previous') }} + {% else %} + < {{ 'admin.display-list.previous' | i18n('Previous') }} + {% endif %} + | + {% if showNext == true %} + {{ 'admin.display-list.next' | i18n('Next') }} > + {% else %} + {{ 'admin.display-list.next' | i18n('Next') }} > + {% endif %} +
    + {% else %} +
     
    + {% endif %} +
    \ No newline at end of file diff --git a/project/apps/back_end/widgets/edit/Edit.class.php b/project/apps/back_end/widgets/edit/Edit.class.php new file mode 100644 index 0000000..c81f9f7 --- /dev/null +++ b/project/apps/back_end/widgets/edit/Edit.class.php @@ -0,0 +1,221 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage edit + */ +class Edit extends \project\apps\back_end\templates\WidgetTemplate { + + protected $errorStack = array(); + protected $model = null; + + /** + * Function - Post Model Save + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + protected function postModelSave(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null){ + $routing->getResponse()->pageRedirect(); + } + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->isSaved = ($routing->getRequest()->getOnceOnlyCookie('edit-form-saved')===null)? false : true; + + if(file_exists($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/'.$_GET['database'].'/'.$_GET['model'].'Table.class.php')){ + $modelTable = \nano\core\db\ORM::getInstance()->getTable($_GET['model'],$_GET['database']); + $fieldInformation = $modelTable->getFieldInformation(); + $fieldNames = array_keys($fieldInformation); + $jsonObj = null; + if(isset($_GET['ids'])){ + $jsonObj = json_decode(base64_decode(rawurldecode($_GET['ids']))); + } + if(is_object($jsonObj)){ + $result = null; + try{ + $query = new \nano\core\db\core\SelectQuery(); + $query->from($modelTable->getTableName())->where($this->getWhere($jsonObj,$fieldNames).' LIMIT 1'); + $results = $modelTable->doSelect($query); + $result = isset($results[0])? $results[0] : null; + } catch (\Exception $e) { + //just log + } + if($result==null){ + $this->errorStack[] = i18n('admin.edit.error.unable-to-find-record','Unable to to find record...'); + } else { + if(!empty($_POST)){ + $this->model = $result[$modelTable->getModelName()]; + //this is a post, so check and see if record should be deleted first... + if(empty($this->errorStack)){ + if($_POST['submit-form']=='delete'){ + try{ + $this->model->delete(); + } catch (\Exception $e) { + //just log + $this->errorStack[] = i18n('admin.edit.error.unable-to-delete','Unable to delete...'); + } + if(empty($this->errorStack)){ + $routing->getResponse()->setOnceOnlyCookie('edit-form-record-deleted',''); + $routing->getResponse()->pageRedirect('/'.$_GET['database'].'/'.$_GET['model'].'/list'); + } + } else { + //this is a post, so try and save + foreach($_POST[$modelTable->getModelName()] as $key => $value){ + try{ + $this->model->$fieldInformation[$key]['set_function']($value); + } catch (\Exception $e) { + $this->errorStack[$key] = i18n('admin.edit.error.could-not-set-value','Could not set value for \'{key}\' as it failed validation',array('{key}' => $key)); + } + } + if(empty($this->errorStack)){ + try{ + $this->model->save(); + $routing->getResponse()->setOnceOnlyCookie('edit-form-saved',''); + if($_POST['submit-form']=='save-preview'){ + $routing->getResponse()->setOnceOnlyCookie('edit-form-record-preview',''); + } + $this->postModelSave($routing,$pageInstance); + } catch (\Exception $e) { + //just log + $this->errorStack[] = i18n('admin.edit.error.unable-to-save','Unable to save...'); + } + } + } + } + } + + $this->editForm = array(); + foreach($fieldInformation as $key => $details){ + $inputType = $this->getType($details); + //check to see if the type can be edited... + if($inputType!==null){ + + if(isset($_POST[$modelTable->getModelName()][$key])){ + $inputValue = $_POST[$modelTable->getModelName()][$key]; + } else { + if($fieldInformation[$key]['is_foreign_reference']){ + $inputValue = $result[$modelTable->getModelName()]->getValueFromKey($fieldInformation[$key]['use_model']); + } else { + $inputValue = $result[$modelTable->getModelName()]->{$fieldInformation[$key]['get_function']}(); + } + } + + $this->editForm[$key] = array( + 'label' => array( + 'value' => i18n('admin.label-for-field.'.$_GET['database'].'.'.$_GET['model'].'.'.$key,$this->getLabel($key,$details)) + ), + 'input' => array( + 'inputType' => $inputType, + 'inputName' => $modelTable->getModelName().'['.$key.']', + 'inputValue' => $inputValue + ) + ); + } + } + + $this->editButton = array( + 'buttonValue' => 'save', + 'buttonLabel' => i18n('admin.edit.save-button-value','Save') + ); + + $this->editAndPreviewButton = array( + 'buttonValue' => 'save-preview', + 'buttonLabel' => i18n('admin.edit.save-and-preview-button-value','Save & Preview') + ); + + $delete = new \project\apps\back_end\widgets\delete\Delete(); + $this->delete = $delete->getRenderedWidget(); + } + } else { + $this->errorStack[] = i18n('admin.edit.error.unable-to-get-keys-from-pk','Unable to to find record...'); + } + } else { + $this->errorStack[] = i18n('admin.edit.error.unable-find-data-model','Unable to find data model...'); + } + + $this->editErrors = $this->errorStack; + $this->databaseName = $_GET['database']; + $this->modelName = $_GET['model']; + + return 'project/apps/back_end/widgets/edit/views/edit.twig'; + } + + /** + * Function - Get Where + * @param mixed $dataObj Data Obj + * @param mixed $fieldNames Field Names + */ + protected function getWhere($dataObj,$fieldNames){ + $where = ''; + foreach($dataObj as $key => $value){ + if($value!=''){ + if(in_array($key,$fieldNames)){ + $where .= '`'.$key.'` = "'.addslashes($value).'" AND '; + } + } + } + $where = preg_replace('/\s{1}AND\s{1}$/','',$where); + return ($where=='')? '1' : $where; + } + + /** + * Function - Get Label + * @param mixed $key Key + */ + protected function getLabel($key){ + $key = preg_replace('/(.*?)\.(.+)$/','$2',$key); + $key = preg_replace('/([a-z]{1})([A-Z]{1})/','$1 $2',$key); + $key = preg_replace('/\_([a-z]{1})/ie',"' '.strtoupper('\\1')",$key); + return ucfirst($key); + } + + /** + * Function - Get Type + * @param mixed $details Details + * @return mixed + */ + protected function getType($details){ + if(preg_match('/^([a-z]+)/',$details['mysql_type'],$matches)){ + switch($matches[1]){ + case 'int': + return 'text'; + break; + case 'varchar': + return 'text'; + break; + case 'char': + return 'text'; + break; + case 'text': + return 'textarea'; + break; + case 'date': + return 'text'; + break; + case 'datetime': + return 'text'; + break; + case 'tinyint': + return 'text'; + break; + case 'smallint': + return 'text'; + break; + } + } + return null; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/edit/views/edit.twig b/project/apps/back_end/widgets/edit/views/edit.twig new file mode 100644 index 0000000..2438aa4 --- /dev/null +++ b/project/apps/back_end/widgets/edit/views/edit.twig @@ -0,0 +1,66 @@ +{% import 'project/apps/back_end/forms/form.twig' as form %} + +{% block outer_script %} + +{% endblock outer_script %} + +{% block form_construct %} +
    +{% endblock form_construct %} +
    +

    {{ 'admin.edit' | i18n('Edit Record') }}

    + + {% if isSaved == true %} +
    {{ 'admin.edit.record.updated' | i18n('Record Updated') }}
    + {% endif %} + + {% for key, value in editErrors %} +
    {{ value }}
    + {% endfor %} + + + {% if editForm %} + {% for key, params in editForm %} + {% block row %} + + + + + {% endblock row %} + {% endfor %} + {% block buttons %} + + + + + {% endblock buttons %} + {% endif %} +
    {{ form.adminLabel(params.label) }}{{ form.buildAdminForm(params) }}
     {{ form.adminButton(editButton) }}{{ form.adminButton(editAndPreviewButton) }}
    +
    + {% block delete %} + {% if editForm %} +
    +

    {{ 'admin.delete-record' | i18n('Delete Record') }}

    +
    {{ delete }}
    +
    + {% endif %} + {% endblock delete %} +
    \ No newline at end of file diff --git a/project/apps/back_end/widgets/export/ModelExport.class.php b/project/apps/back_end/widgets/export/ModelExport.class.php new file mode 100644 index 0000000..3d624bd --- /dev/null +++ b/project/apps/back_end/widgets/export/ModelExport.class.php @@ -0,0 +1,44 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage export + */ +class ModelExport extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + * @return mixed + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + if(file_exists($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/'.$_GET['database'].'/'.$_GET['model'].'Table.class.php')){ + $modelTable = \nano\core\db\ORM::getInstance()->getTable($_GET['model'],$_GET['database']); + $this->fieldInformation = $modelTable->getFieldInformation(); + $this->fieldNames = array_keys($this->fieldInformation); + + $this->database = $_GET['database']; + $this->model = $_GET['model']; + + if($routing->isAjax()){ + $query = new \nano\core\db\core\SelectQuery(); + $query->from($modelTable->getTableName())->where('1'); + $this->results = $modelTable->doSelect($query); + return 'project/apps/back_end/widgets/export/views/model_export_'.$_GET['type'].'.php'; + } + } + + return 'project/apps/back_end/widgets/export/views/model_export.twig'; + + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/export/views/model_export.twig b/project/apps/back_end/widgets/export/views/model_export.twig new file mode 100644 index 0000000..32b61fe --- /dev/null +++ b/project/apps/back_end/widgets/export/views/model_export.twig @@ -0,0 +1,15 @@ +{% import 'project/apps/back_end/forms/form.twig' as form %} + + + +
    +

    {{ 'admin.model-export' | i18n('Export') }}

    + Export As CSV +
    \ No newline at end of file diff --git a/project/apps/back_end/widgets/export/views/model_export_csv.php b/project/apps/back_end/widgets/export/views/model_export_csv.php new file mode 100644 index 0000000..c112d80 --- /dev/null +++ b/project/apps/back_end/widgets/export/views/model_export_csv.php @@ -0,0 +1,30 @@ +0){ + foreach($fieldNames as $fieldName){ + if($fieldInformation[$fieldName]['is_foreign_reference']){ + $fieldName = $fieldInformation[$fieldName]['use_model']; + } + $columnString .= '"'.preg_replace('/\"/','""',$fieldName).'",'; + } + echo substr($columnString,0,-1)."\n\r"; + foreach($results as $result){ + //true value should be fetched through 'get_function'. Reason is when uploading back, 'set_function' will and should be used... + $rowString = ''; + foreach($fieldNames as $fieldName){ + if($fieldInformation[$fieldName]['is_foreign_reference']){ + $value = $result[$model]->getValueFromKey($fieldInformation[$fieldName]['use_model']); + } else { + $value = $result[$model]->{$fieldInformation[$fieldName]['get_function']}(); + } + $rowString .= '"'.preg_replace('/\"/','""',$value).'",'; + } + echo substr($rowString,0,-1)."\n\r"; + } +} + +?> \ No newline at end of file diff --git a/project/apps/back_end/widgets/filter/Filter.class.php b/project/apps/back_end/widgets/filter/Filter.class.php new file mode 100644 index 0000000..9b18595 --- /dev/null +++ b/project/apps/back_end/widgets/filter/Filter.class.php @@ -0,0 +1,99 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage filter + */ +class Filter extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->filterErrors = array(); + + if(file_exists($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/'.$_GET['database'].'/'.$_GET['model'].'Table.class.php')){ + $modelTable = \nano\core\db\ORM::getInstance()->getTable($_GET['model'],$_GET['database']); + $fieldInformation = $modelTable->getFieldInformation(); + + $this->filterForm = array(); + foreach($fieldInformation as $key => $details){ + $inputValue = isset($_POST['filter'][$key])? $_POST['filter'][$key] : ''; + $this->filterForm[$key] = array( + 'label' => array( + 'value' => i18n('admin.label-for-field.'.$_GET['database'].'.'.$_GET['model'].'.'.$key,$this->getLabel($key,$details)) + ), + 'input' => array( + 'inputType' => $this->getType($details), + 'inputName' => 'filter['.$key.']', + 'inputValue' => $inputValue + ) + ); + } + + $this->filterButton = array( + 'buttonValue' => 'Apply Filter', + 'buttonLabel' => i18n('admin.filter.apply-filter-button-value','Apply Filter') + ); + + } else { + $this->filterErrors = i18n('admin.filter.error.model-not-found','Unable to find data model...'); + } + return 'project/apps/back_end/widgets/filter/views/filter.twig'; + } + + /** + * Function - Get Label + * @param mixed $key Key + * @param mixed $details Details + */ + protected function getLabel($key,$details){ + $key = preg_replace('/(.*?)\.(.+)$/','$2',$key); + $key = preg_replace('/([a-z]{1})([A-Z]{1})/','$1 $2',$key); + $key = preg_replace('/\_([a-z]{1})/ie',"' '.strtoupper('\\1')",$key); + return ucfirst($key); + } + + /** + * Function - Get Type + * @param mixed $details Details + * @return mixed + */ + protected function getType($details){ + if(preg_match('/^([a-z]+)/',$details['mysql_type'],$matches)){ + switch($matches[1]){ + case 'int': + return 'text'; + break; + case 'varchar': + return 'text'; + break; + case 'char': + return 'text'; + break; + case 'text': + return 'text'; + break; + case 'date': + return 'text'; + break; + case 'datetime': + return 'text'; + break; + } + } + return 'text'; //default base case + } + + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/filter/views/filter.twig b/project/apps/back_end/widgets/filter/views/filter.twig new file mode 100644 index 0000000..3583df5 --- /dev/null +++ b/project/apps/back_end/widgets/filter/views/filter.twig @@ -0,0 +1,33 @@ +{% import 'project/apps/back_end/forms/form.twig' as form %} + +{% block outer_script %} + +{% endblock outer_script %} + +
    +

    {{ 'admin.filter' | i18n('Filter') }}

    +
    + + {% for key, params in filterForm %} + + + + + {% endfor %} + + + + +
    {{ form.adminLabel(params.label) }}{{ form.buildAdminForm(params) }}
     {{ form.adminButton(filterButton) }}
    +
    +
    \ No newline at end of file diff --git a/project/apps/back_end/widgets/header/Header.class.php b/project/apps/back_end/widgets/header/Header.class.php new file mode 100644 index 0000000..e4bbfb3 --- /dev/null +++ b/project/apps/back_end/widgets/header/Header.class.php @@ -0,0 +1,61 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage header + */ +class Header extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Add Style Sheet + * @param mixed $path Path + */ + public function addStyleSheet($path) { + $this->styleSheets[] = $path; + } + + /** + * Function - Add Java Script + * @param mixed $path Path + */ + public function addJavaScript($path) { + $this->javaScripts[] = $path; + } + + /** + * Function - Set Title + * @param mixed $title Title + */ + public function setTitle($title) { + $this->title = $title; + } + + /** + * Function - Pre Load + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + protected function preLoad(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + $this->styleSheets = array(); + $this->javaScripts = array(); + $this->title = ''; + } + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + * @return mixed + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + return 'project/apps/back_end/widgets/header/views/header.php'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/header/views/header.php b/project/apps/back_end/widgets/header/views/header.php new file mode 100644 index 0000000..ccf3f05 --- /dev/null +++ b/project/apps/back_end/widgets/header/views/header.php @@ -0,0 +1,21 @@ + + + + '."\n"; + } + ?> + + + + + + + <?php echo $title; ?> + '."\n"; + } + ?> + \ No newline at end of file diff --git a/project/apps/back_end/widgets/import/ModelImport.class.php b/project/apps/back_end/widgets/import/ModelImport.class.php new file mode 100644 index 0000000..1d9de11 --- /dev/null +++ b/project/apps/back_end/widgets/import/ModelImport.class.php @@ -0,0 +1,91 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage import + */ +class ModelImport extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->importErrors = array(); + $this->isSaved = false; + + if(file_exists($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/'.$_GET['database'].'/'.$_GET['model'].'Table.class.php')){ + $modelTable = \nano\core\db\ORM::getInstance()->getTable($_GET['model'],$_GET['database']); + $fieldInformation = $modelTable->getFieldInformation(); + $fieldNames = array_keys($fieldInformation); + if(!empty($_FILES)){ + //file has been uploaded, so process + switch($_FILES['import-file']['type']){ + case 'text/csv': + $modelClassName = 'project\db\om\\'.$_GET['database'].'\\'.$_GET['model']; + $mapClassName = 'project\db\om\\'.$_GET['database'].'\map\Map'; + if(($handle = fopen($_FILES['import-file']['tmp_name'],'r')) !== false){ + $cols = fgetcsv($handle,0,',','"','"'); + if($cols !== false){ + while(($data = fgetcsv($handle,0,',','"','"')) !== false){ + if(count($data)===count($cols)){ + $model = new $modelClassName(); + foreach($cols as $key => $col){ + if(isset($mapClassName::$mapTablesToColumns[$modelTable->getTableName()][$col])){ + try{ + $model->{$fieldInformation[$mapClassName::$mapTablesToColumns[$modelTable->getTableName()][$col]]['set_function']}($data[$key]); + } catch (\Exception $e) { + $this->importErrors[] = $e->getMessage(); + } + } else { + $this->importErrors[] = 'Database column `'.$col.'` not found in model map'; + } + } + try{ + $model->save(); + $this->isSaved = true; + } catch (\Exception $e) { + $this->importErrors[] = $e->getMessage(); + } + } + } + } + } else { + $this->importErrors[] = 'Can\'t open file on the server'; + } + fclose($handle); + break; + default: + $this->importErrors[] = 'File type is not supported'; + break; + } + + } + $this->importForm[] = array( + 'label' => array( + 'value' => 'Please choose a file to upload' + ), + 'input' => array( + 'inputType' => 'file', + 'inputName' => 'import-file', + 'inputValue' => '' + ) + ); + + $this->importButton = array( + 'buttonValue' => 'upload-file', + 'buttonLabel' => 'Upload File' + ); + } + return 'project/apps/back_end/widgets/import/views/model_import.twig'; + } +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/import/views/model_import.twig b/project/apps/back_end/widgets/import/views/model_import.twig new file mode 100644 index 0000000..708ed24 --- /dev/null +++ b/project/apps/back_end/widgets/import/views/model_import.twig @@ -0,0 +1,37 @@ +{% import 'project/apps/back_end/forms/form.twig' as form %} + + + +
    +
    +

    {{ 'admin.model-import' | i18n('Import') }}

    + {% if isSaved == true %} +
    File has been successfully imported
    + {% endif %} + + {% for key, value in importErrors %} +
    {{ value }}
    + {% endfor %} + + + {% for key, params in importForm %} + + + + + {% endfor %} + + + + +
    {{ form.adminLabel(params.label) }}{{ form.buildAdminForm(params) }}
     {{ form.adminButton(importButton) }}
    +
    +
    + \ No newline at end of file diff --git a/project/apps/back_end/widgets/menu/Menu.class.php b/project/apps/back_end/widgets/menu/Menu.class.php new file mode 100644 index 0000000..ebe8bd6 --- /dev/null +++ b/project/apps/back_end/widgets/menu/Menu.class.php @@ -0,0 +1,16 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage menu + */ +class Menu extends base\BaseMenu { + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/menu/base/BaseMenu.class.php b/project/apps/back_end/widgets/menu/base/BaseMenu.class.php new file mode 100644 index 0000000..d9ae9ce --- /dev/null +++ b/project/apps/back_end/widgets/menu/base/BaseMenu.class.php @@ -0,0 +1,49 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package menu + * @subpackage base + */ +class BaseMenu extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Set List Type + * @param mixed $listType List Type + */ + public function setListType($listType){ + $this->listType = $listType; + } + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->isSuperAdmin = \project\session\Session::isSuperAdmin(); + + $databases = array_keys(\nano\core\helpers\FileSystem::getFoldersIn($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/')); + foreach($databases as $database){ + $mapFile = 'project\db\om\\'.$database.'\map\Map'; + $models = $mapFile::$modelNameToTableName; + foreach($models as $modelName => $modelNiceName){ + $models[$modelName] = preg_replace('/([a-z]{1})([A-Z]{1})/','$1 $2',$modelName).'s'; + $models[$modelName] = preg_replace('/ys$/','ies',$models[$modelName]); + } + $this->menu[$database] = $models; + } + if($this->listType=='blocks'){ + return 'project/apps/back_end/widgets/menu/views/menu_blocks.twig'; + } + return 'project/apps/back_end/widgets/menu/views/menu.twig'; + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/menu/views/menu.twig b/project/apps/back_end/widgets/menu/views/menu.twig new file mode 100644 index 0000000..0c5908b --- /dev/null +++ b/project/apps/back_end/widgets/menu/views/menu.twig @@ -0,0 +1,30 @@ + + + + +
    +

    {{ 'admin.actions' | i18n('Actions') }}

    + +
    \ No newline at end of file diff --git a/project/apps/back_end/widgets/menu/views/menu_blocks.twig b/project/apps/back_end/widgets/menu/views/menu_blocks.twig new file mode 100644 index 0000000..06c76fa --- /dev/null +++ b/project/apps/back_end/widgets/menu/views/menu_blocks.twig @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/project/apps/back_end/widgets/view/View.class.php b/project/apps/back_end/widgets/view/View.class.php new file mode 100644 index 0000000..ec6a8a4 --- /dev/null +++ b/project/apps/back_end/widgets/view/View.class.php @@ -0,0 +1,90 @@ + & Alessandro Barzanti + * @version SVN: $id + * @package widgets + * @subpackage view + */ +class View extends \project\apps\back_end\templates\WidgetTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + if(file_exists($_SERVER['SCRIPTS_LOAD_FROM'].'project/db/om/'.$_GET['database'].'/'.$_GET['model'].'Table.class.php')){ + $modelTable = \nano\core\db\ORM::getInstance()->getTable($_GET['model'],$_GET['database']); + $fieldInformation = $modelTable->getFieldInformation(); + $fieldNames = array_keys($fieldInformation); + $jsonObj = json_decode(base64_decode(rawurldecode($_GET['ids']))); + $result = null; + + try{ + $query = new \nano\core\db\core\SelectQuery(); + $query->from($modelTable->getTableName())->where($this->getWhere($jsonObj,$fieldNames).' LIMIT 1'); + $results = $modelTable->doSelect($query); + $result = isset($results[0])? $results[0] : null; + } catch (\Exception $e) { + //just log + } + + $this->displayItems = array(); + foreach($fieldNames as $fieldName){ + + if($fieldInformation[$fieldName]['is_foreign_reference']){ + $value = $result[$modelTable->getModelName()]->getValueFromKey($fieldInformation[$fieldName]['use_model']); + } else { + $value = $result[$modelTable->getModelName()]->getValueFromKey($fieldName); + //$value = $result[$modelTable->getModelName()]->{$fieldInformation[$fieldName]['get_function']}(); + } + + $this->displayItems[$fieldName] = array( + 'nice_name' => i18n('admin.label-for-field.'.$_GET['database'].'.'.$_GET['model'].'.'.$fieldName,$this->getLabel($fieldName)), + 'value' => $value + ); + } + $this->databaseName = $_GET['database']; + $this->modelName = $_GET['model']; + } + + return 'project/apps/back_end/widgets/view/views/view.twig'; + } + + /** + * Function - Get Where + * @param mixed $dataObj Data Obj + * @param mixed $fieldNames Field Names + */ + protected function getWhere($dataObj,$fieldNames){ + $where = ''; + foreach($dataObj as $key => $value){ + if($value!=''){ + if(in_array($key,$fieldNames)){ + $where .= '`'.$key.'` = "'.addslashes($value).'" AND '; + } + } + } + $where = preg_replace('/\s{1}AND\s{1}$/','',$where); + return ($where=='')? '1' : $where; + } + + /** + * Function - Get Label + * @param mixed $key Key + */ + protected function getLabel($key){ + $key = preg_replace('/(.*?)\.(.+)$/','$2',$key); + $key = preg_replace('/([a-z]{1})([A-Z]{1})/','$1 $2',$key); + $key = preg_replace('/\_([a-z]{1})/ie',"' '.strtoupper('\\1')",$key); + return ucfirst($key); + } + +} \ No newline at end of file diff --git a/project/apps/back_end/widgets/view/views/view.twig b/project/apps/back_end/widgets/view/views/view.twig new file mode 100644 index 0000000..3b0e088 --- /dev/null +++ b/project/apps/back_end/widgets/view/views/view.twig @@ -0,0 +1,14 @@ +
    +
    +

    {{ 'admin.view-record' | i18n('View Record') }}

    + + {% for key, value in displayItems %} + + + + + {% endfor %} +
    {{ value.nice_name }}: {{ value.value }}
    +
    +
    +{{ 'log' | inc }} \ No newline at end of file diff --git a/project/cli/generate/DatabaseModels.class.php b/project/cli/generate/DatabaseModels.class.php new file mode 100644 index 0000000..49b5e51 --- /dev/null +++ b/project/cli/generate/DatabaseModels.class.php @@ -0,0 +1,367 @@ +termPrintln('Please set the correct environment to use with --environment=Dev (for example)...', self::FORMAT_BLUE, self::FORMAT_BG_WHITE, self::FORMAT_BOLD); + $this->termPrintln('--connection=[default=\'default\']'); + $this->termPrintln('--database=[default=\'all\']'); + $this->termPrintln('--table=[default=\'all\']'); + } else { + if(file_exists('project/config/environments/'.$environment.'.class.php')){ + \nano\core\config\Config::setEnvironment($environment); + $databases = \nano\core\config\Config::get('databases'); + if($connection==null) { + if(isset($databases['default'])){ + echo 'Using "default" connection'."\n"; + $this->fetchDatabases('default',$database,$table); + } else { + echo 'FAILED: no "default" connection found under databases, please specify a database using --database=dbname (for example)...'."\n"; + } + } else { + if(isset($databases[$connection])){ + echo 'Using "'.$connection.'" connection'."\n"; + $this->fetchDatabases($connection,$database,$table); + } else { + echo 'FAILED: no "'.$connection.'" connection found under databases, please specify a database using --database=dbname (for example)...'."\n"; + } + } + } else { + echo 'FAILED: environment "'.$environment.'" not found. Please check in project/config/environments...'."\n"; + } + } + } + + private function fetchDatabases($connection,$database,$table) { + $db = \nano\core\db\core\Database::open($connection); + if($db->isOpen()){ + $results = $db->select('SHOW DATABASES'); + $databases = array(); + foreach($results as $result) { + $databases[] = $result['Database']; + } + if($database!=null){ + if(!in_array($database,$databases)){ + echo 'FAILED: the following database does not appear to exist "'.$database.'"...'."\n"; + } else { + $this->fetchTables($db,$connection,$database,$table); + } + } else { + if($table==null){ + foreach($databases as $database){ + $this->fetchTables($db,$connection,$database); + } + } else { + echo 'FAILED: please specify a database in which the table is present...'."\n"; + } + } + } + } + + private function fetchTables($db,$connection,$database,$table=null) { + $databaseInformation = array(); + if($db->isOpen()){ + $results = $db->select('SHOW TABLES IN '.$database); + $map = array(); + foreach($results as $result) { + $map[$result['Tables_in_'.$database]] = $this->getModelNameFromTableName($result['Tables_in_'.$database]); + if($table==null) { + $databaseInformation[] = $this->generateModel($db,$connection,$database,$result['Tables_in_'.$database]); + } else if($table==$result['Tables_in_'.$database]) { + $databaseInformation[] = $this->generateModel($db,$connection,$database,$result['Tables_in_'.$database]); + } + } + if($table==null){ + $this->generateMap($map,$database,$databaseInformation); + } else { + echo 'WARNING: When updating a single table, the map file is NOT updated. It is recommended to generate from --database=[database] where possible...'; + } + } + } + + private function getModelNameFromTableName($tableName){ + $partModelNames = explode('_',$tableName); + $modelName = ''; + if(count($partModelNames)>1){ + foreach($partModelNames as $partModelName){ + $modelName .= ucfirst($partModelName); + } + } else { + $modelName = ucfirst($tableName); + } + return preg_replace('/s$/','',$modelName); + } + + private function generateMap($map,$database,$databaseInformation){ + $folderPath = 'project/db/om/'.$database.'/map'; + if(!file_exists($folderPath)){ + mkdir($folderPath); + } + $mapPath = $folderPath.'/Map.class.php'; + + $classBody = "\t".'public static $tableNameToModelName = array('."\n"; + if(count($map)>0){ + foreach($map as $tableName => $modelName){ + $classBody .= "\t\t".'\''.$tableName.'\' => \''.$modelName.'\','."\n"; + } + $classBody = substr($classBody,0,-2)."\n"; + } + $classBody .= "\t".');'."\n"; + + $classBody .= "\t".'public static $modelNameToTableName = array('."\n"; + if(count($map)>0){ + foreach($map as $tableName => $modelName){ + $classBody .= "\t\t".'\''.$modelName.'\' => \''.$tableName.'\','."\n"; + } + $classBody = substr($classBody,0,-2)."\n"; + } + $classBody .= "\t".');'."\n"; + + $classBody .= "\t".'public static $mapTablesToColumns = array('."\n"; + if(count($map)>0){ + foreach($databaseInformation as $tables){ + $tableName = key($tables); + $classBody .= "\t\t".'\''.$tableName.'\' => array('; + if(count($tables)>0){ + foreach($tables[$tableName] as $column){ + $columnKey = $column; + if(preg_match('/^((?.*?)\.)?(?[A-Z]{1}.+?)$/',$column,$matches)){ + $columnKey = $matches['model']; + } + $classBody .= '\''.$columnKey.'\' => \''.$column.'\','; + } + $classBody = substr($classBody,0,-1); + } + $classBody .= '),'."\n"; + } + $classBody = substr($classBody,0,-2)."\n"; + } + $classBody .= "\t".');'."\n"; + + $fileBody = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newMap.php', + array( + 'database' => $database, + 'classBody' => $classBody + ), + true + ); + file_put_contents($mapPath,$fileBody); + } + + private function generateModel($db,$connection,$database,$table){ + $tableInformation = array(); + echo 'Generating new model, base model, model table and base model table for database:"'.$database.'" and table:"'.$table.'"'."\n"; + if($db->isOpen()){ + $columns = $db->select('SHOW COLUMNS IN '.$table.' IN '.$database); + $modelName = $this->getModelNameFromTableName($table); + //check folders exist + $folderPath = 'project/db/om/'.$database; + if(!file_exists($folderPath)){ + mkdir($folderPath); + mkdir($folderPath.'/base'); + } + + $modelPath = $folderPath.'/'.$modelName.'.class.php'; + $modelTablePath = $folderPath.'/'.$modelName.'Table.class.php'; + $baseModelPath = $folderPath.'/base/Base'.$modelName.'.class.php'; + $baseModelTablePath = $folderPath.'/base/Base'.$modelName.'Table.class.php'; + if(!file_exists($modelPath)){ + $fileBody = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newModel.php', + array( + 'database' => $database, + 'modelName' => $modelName, + 'classBody' => '' + ), + true + ); + file_put_contents($modelPath,$fileBody); + } + if(!file_exists($modelTablePath)){ + $fileBody = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newModelTable.php', + array( + 'database' => $database, + 'modelName' => $modelName, + 'classBody' => '' + ), + true + ); + file_put_contents($modelTablePath,$fileBody); + } + + $primaryKeys = array(); + $fieldNameMapStr = "\t".'protected $newFieldNameMap = array('."\n"; + $fieldsStr = "\t".'protected $fields = array('."\n"; + $validationFunctionStrs = array(); + $setFunctionStrs = array(); + $getFunctionStrs = array(); + if(count($columns)>0){ + $columnInformation = array(); + foreach($columns as $column){ + + $useModel = $modelName; + $useDatabase = $database; + $isForeignReference = 'false'; + $newFieldName = $column['Field']; + + if(preg_match('/^((?.+?)\.)?(?[A-Z]{1}.+?)$/',$column['Field'],$matches)){ + $useModel = $matches['model']; + $newFieldName = $useModel; + if(isset($matches['database'])){ + if($matches['database']!=''){ + $useDatabase = $matches['database']; + } + } + $isForeignReference = 'true'; + } + + $columnInformation[] = $column['Field']; + + if($column['Key']=='PRI'){ + $primaryKeys[] = $newFieldName; + } + + $partColumnNames = explode('_',$newFieldName); + + $setFunctionName = 'set'; + $getFunctionName = 'get'; + $validationFunctionName = 'validate'; + if(count($partColumnNames)>1){ + foreach($partColumnNames as $partColumnName){ + $setFunctionName .= ucfirst($partColumnName); + $getFunctionName .= ucfirst($partColumnName); + $validationFunctionName .= ucfirst($partColumnName); + } + } else { + $getFunctionName .= ucfirst($newFieldName); + $setFunctionName .= ucfirst($newFieldName); + $validationFunctionName .= ucfirst($newFieldName); + } + + $setFunctionStr = "\t".'public function '.$setFunctionName.'($value){'."\n"; + $setFunctionStr .= "\t\t".'if(\project\db\om\\'.$database.'\\'.$modelName.'::'.$validationFunctionName.'($value)){'."\n"; + $setFunctionStr .= "\t\t\t".'$this->'.$newFieldName.' = $value;'."\n"; + $setFunctionStr .= "\t\t".'} else {'."\n"; + $setFunctionStr .= "\t\t\t".'throw new \nano\core\exception\ValidationException(\'Validation of column `'.$column['Field'].'` failed\');'."\n"; + $setFunctionStr .= "\t\t".'}'."\n"; + $setFunctionStr .= "\t".'}'."\n"; + $setFunctionStrs[] = $setFunctionStr; + + $getFunctionStrs[] = "\t".'public function '.$getFunctionName.'(){'."\n\t\t".'return $this->'.$newFieldName.';'."\n\t".'}'."\n"; + $validationFunctionStrs[] = "\t".'public static function '.$validationFunctionName.'($value){'."\n\t\t".'return true;'."\n\t".'}'."\n"; + + $fieldNameMapStr .= "\t\t".'\''.$newFieldName.'\' => \''.$column['Field'].'\','."\n"; + + $fieldsStr .= "\t\t".'\''.$column['Field'].'\' => array('."\n"; + + $fieldsStr .= "\t\t\t".'\'mysql_type\' => \''.$column['Type'].'\','."\n"; + $fieldsStr .= "\t\t\t".'\'mysql_is_null\' => \''.$column['Null'].'\','."\n"; + $fieldsStr .= "\t\t\t".'\'mysql_key\' => \''.$column['Key'].'\','."\n"; + $fieldsStr .= "\t\t\t".'\'mysql_default\' => \''.$column['Default'].'\','."\n"; + $fieldsStr .= "\t\t\t".'\'mysql_extra\' => \''.$column['Extra'].'\','."\n"; + $fieldsStr .= "\t\t\t".'\'is_foreign_reference\' => '.$isForeignReference.','."\n"; + $fieldsStr .= "\t\t\t".'\'use_model\' => \''.$useModel.'\','."\n"; + $fieldsStr .= "\t\t\t".'\'use_database\' => \''.$useDatabase.'\','."\n"; + $fieldsStr .= "\t\t\t".'\'set_function\' => \''.$setFunctionName.'\','."\n"; + $fieldsStr .= "\t\t\t".'\'validation_function\' => \''.$validationFunctionName.'\','."\n"; + $fieldsStr .= "\t\t\t".'\'get_function\' => \''.$getFunctionName.'\','."\n"; + + $fieldsStr .= "\t\t".'),'."\n"; + } + $tableInformation[$table] = $columnInformation; + $fieldNameMapStr = substr($fieldNameMapStr,0,-2)."\n"; + $fieldsStr = substr($fieldsStr,0,-2)."\n"; + } + $fieldsStr .= "\t".');'."\n"; + $fieldNameMapStr .= "\t".');'."\n"; + + $retrieveByPkStr = ''; + + $primaryKeyStr = ''; + if(count($primaryKeys)>0){ + $retrieveByPkStr = "\t".'public function retrieveByPk('; + $primaryKeyStr = "\t".'protected $primaryKey = array('; + foreach($primaryKeys as $primaryKey){ + $primaryKeyStr .= '\''.$primaryKey.'\','; + $retrieveByPkStr .= '$'.$primaryKey.','; + } + $primaryKeyStr = substr($primaryKeyStr,0,-1).');'."\n"; + $retrieveByPkStr = substr($retrieveByPkStr,0,-1).') {'."\n"; + $retrieveByPkStr .= "\t\t".'$query = new \nano\core\db\core\SelectQuery();'."\n"; + $retrieveByPkStr .= "\t\t".'$query->from(\''.$table.'\')->where(\''; + foreach($primaryKeys as $primaryKey){ + $retrieveByPkStr .= '`'.$table.'`.`'.$primaryKey.'` = "\'.addslashes($'.$primaryKey.').\'" AND '; + } + $retrieveByPkStr = substr($retrieveByPkStr,0,-4).'LIMIT 1\');'."\n"; + $retrieveByPkStr .= "\t\t".'$results = $this->doSelect($query);'."\n"; + $retrieveByPkStr .= "\t\t".'return isset($results[0][\''.$modelName.'\'])? $results[0][\''.$modelName.'\'] : null;'."\n"; + $retrieveByPkStr .= "\t".'}'."\n"; + } else if(count($primaryKeys)==1) { + $primaryKeyStr = "\t".'protected $primaryKey = \'array()\';'."\n"; + } + + $classBody = "\n"; + $classBody .= "\t".'protected $modelName = \''.$modelName.'\';'."\n"; + $classBody .= $primaryKeyStr; + $classBody .= "\t".'protected $dbConfig = \''.$connection.'\';'."\n"; + $classBody .= "\t".'protected $dbName = \''.$database.'\';'."\n"; + $classBody .= "\t".'protected $tableName = \''.$table.'\';'."\n"; + $classBody .= $fieldsStr; + $classBody .= $fieldNameMapStr; + + $fileBody = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newBaseModelTable.php', + array( + 'database' => $database, + 'modelName' => $modelName, + 'classBody' => $classBody, + 'retrieveByPk' => $retrieveByPkStr + ), + true + ); + file_put_contents($baseModelTablePath,$fileBody); + + foreach($setFunctionStrs as $setFunctionStr){ + $classBody .= $setFunctionStr; + } + + foreach($getFunctionStrs as $getFunctionStr){ + $classBody .= $getFunctionStr; + } + + foreach($validationFunctionStrs as $validationFunctionStr){ + $classBody .= $validationFunctionStr; + } + + $fileBody = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newBaseModel.php', + array( + 'database' => $database, + 'modelName' => $modelName, + 'classBody' => $classBody + ), + true + ); + file_put_contents($baseModelPath,$fileBody); + } + return $tableInformation; + } +} \ No newline at end of file diff --git a/project/cli/generate/NewApp.class.php b/project/cli/generate/NewApp.class.php new file mode 100644 index 0000000..affd685 --- /dev/null +++ b/project/cli/generate/NewApp.class.php @@ -0,0 +1,159 @@ +getProjectName(); + //------ + //create project folder + //------ + mkdir('project/apps/'.$projectName); + //------ + //generate new example routing file + //------ + $newProjectRouting = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newProjectRouting.php', + array( + 'projectName' => $projectName + ), + true + ); + //------ + //generate new example config file + //------ + file_put_contents('project/apps/'.$projectName.'/Routing.class.php',$newProjectRouting); + $newProjectConfig = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newProjectConfig.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/Config.class.php',$newProjectConfig); + //------ + //generate new example folder structure + //------ + mkdir('project/apps/'.$projectName.'/pages/index/views',0777,true); + mkdir('project/apps/'.$projectName.'/templates',0777,true); + mkdir('project/apps/'.$projectName.'/layouts',0777,true); + mkdir('project/apps/'.$projectName.'/widgets/header/views',0777,true); + //------ + //generate new example page + //------ + $newExampleProjectPage = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newExampleProjectIndexPage.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/pages/index/Index.class.php',$newExampleProjectPage); + //------ + //generate new example page view + //------ + $newExampleProjectPageView = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newExampleProjectIndexPageView.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/pages/index/views/index.twig',$newExampleProjectPageView); + //------ + //generate new example template + //------ + $newExampleProjectTemplateClass = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newExampleProjectTemplateClass.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/templates/PageTemplate.class.php',$newExampleProjectTemplateClass); + //------ + //generate new example layout + //------ + $newExampleProjectLayout = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newExampleProjectTwigLayout.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/layouts/layout.twig',$newExampleProjectLayout); + //------ + //generate new example widget + //------ + $newExampleProjectHeaderWidget = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newExampleProjectHeaderWidget.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/widgets/header/Header.class.php',$newExampleProjectHeaderWidget); + //------ + //generate new example widget view + //------ + $newExampleProjectHeaderWidgetView = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newExampleProjectHeaderWidgetView.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/widgets/header/views/header.php',$newExampleProjectHeaderWidgetView); + //------ + //generate new example unit tests + //------ + mkdir('project/apps/'.$projectName.'/tests/unit/conf',0777,true); + mkdir('project/apps/'.$projectName.'/tests/unit/lib',0777,true); + $newUnitBootStrap = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newUnitBootStrap.php', + array( + + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/tests/unit/conf/bootstrap.php',$newUnitBootStrap); + $newUnitConf = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newUnitConf.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/tests/unit/conf/phpunit.xml',$newUnitConf); + $newUnitTestExample = \nano\core\view\Twiglet::load( + 'project/cli/generate/views/newUnitTestExample.php', + array( + 'projectName' => $projectName + ), + true + ); + file_put_contents('project/apps/'.$projectName.'/tests/unit/lib/ExampleTest.php',$newUnitTestExample); + + $this->termPrintln('You new project "'.$projectName.'" has been created in project/apps', self::FORMAT_GREEN, self::FORMAT_BOLD); + } + + private function getProjectName() { + try{ + $projectName = $this->readLine('Please enter a name for the new project','/^[a-z]{1}[a-z\_]*$/',$retries=2,'Must start with a letter (a-z) which is lowercase and only contain characters a-z and \'_\''); + } catch (\Exception $e) { + echo 'Failed to get an acceptable name for your new project'; + exit; + } + if(file_exists('project/apps/'.$projectName)){ + $this->termPrintln('It looks like the project name "'.$projectName.'" already exists...', self::FORMAT_RED); + return $this->getProjectName(); + } else { + return $projectName; + } + } +} \ No newline at end of file diff --git a/project/cli/generate/NewCron.class.php b/project/cli/generate/NewCron.class.php new file mode 100644 index 0000000..d244f5c --- /dev/null +++ b/project/cli/generate/NewCron.class.php @@ -0,0 +1,31 @@ + $matches[1] + ), + true + ); + file_put_contents($filePath,$fileBody); + echo 'The file ('.$filePath.') has been generated!'."\n"; + }else{ + echo 'Opps, it looks like that file ('.$filePath.') already exists!'."\n"; + } + }else{ + echo 'Please enter the name of the cron file in the form \'--name=Example\''."\n"; + } + } +} \ No newline at end of file diff --git a/project/cli/generate/NewTask.class.php b/project/cli/generate/NewTask.class.php new file mode 100644 index 0000000..edbdc5a --- /dev/null +++ b/project/cli/generate/NewTask.class.php @@ -0,0 +1,31 @@ + $matches[1] + ), + true + ); + file_put_contents($filePath,$fileBody); + echo 'The file ('.$filePath.') has been generated!'."\n"; + }else{ + echo 'Opps, it looks like that file ('.$filePath.') already exists!'."\n"; + } + }else{ + echo 'Please enter the name of the task file in the form \'--name=Example\''."\n"; + } + } +} \ No newline at end of file diff --git a/project/cli/generate/views/newBaseModel.php b/project/cli/generate/views/newBaseModel.php new file mode 100644 index 0000000..6ab8957 --- /dev/null +++ b/project/cli/generate/views/newBaseModel.php @@ -0,0 +1,30 @@ + + * @version SVN: $id + * @package {{ database }} + * @subpackage base + */ +class Base{{ modelName }} extends \nano\core\db\om\Base { +{{ classBody }} +} \ No newline at end of file diff --git a/project/cli/generate/views/newBaseModelTable.php b/project/cli/generate/views/newBaseModelTable.php new file mode 100644 index 0000000..793be28 --- /dev/null +++ b/project/cli/generate/views/newBaseModelTable.php @@ -0,0 +1,31 @@ + + * @version SVN: $id + * @package {{ database }} + * @subpackage base + */ +class Base{{ modelName }}Table extends \nano\core\db\om\BaseTable { +{{ classBody }} +{{ retrieveByPk }} +} \ No newline at end of file diff --git a/project/cli/generate/views/newCron.php b/project/cli/generate/views/newCron.php new file mode 100644 index 0000000..8f48a6f --- /dev/null +++ b/project/cli/generate/views/newCron.php @@ -0,0 +1,23 @@ + + * @version SVN: $id + * @package cli + * @subpackage cron + */ +class {{ className }} extends \nano\core\cli\CronBase { + + /** + * Function - Execute + */ + public function execute() { + echo 'Your code here...'."\n"; + } + +} \ No newline at end of file diff --git a/project/cli/generate/views/newExampleProjectHeaderWidget.php b/project/cli/generate/views/newExampleProjectHeaderWidget.php new file mode 100644 index 0000000..58559f3 --- /dev/null +++ b/project/cli/generate/views/newExampleProjectHeaderWidget.php @@ -0,0 +1,61 @@ + + * @version SVN: $id + * @package widgets + * @subpackage header + */ +class Header extends \nano\core\widget\Widget { + + /** + * Function - Add Style Sheet File + * @param $path Path To Style Sheet File + */ + public function addStyleSheet($path) { + $this->styleSheets[] = $path; + } + + /** + * Function - Add JavaScript File + * @param $path Path To JavaScript File + */ + public function addJavaScript($path) { + $this->javaScripts[] = $path; + } + + /** + * Function - Set Title + * @param $title Page Title + */ + public function setTitle($title) { + $this->title = $title; + } + + /** + * Function - Pre Load + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + */ + protected function preLoad(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + $this->styleSheets = array(); + $this->javaScripts = array(); + $this->title = ''; + } + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + * @param \nano\core\page\Page $pageInstance Page Instance + * @return Template Location + */ + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + return 'project/apps/{{ projectName }}/widgets/header/views/header.php'; + } + +} \ No newline at end of file diff --git a/project/cli/generate/views/newExampleProjectHeaderWidgetView.php b/project/cli/generate/views/newExampleProjectHeaderWidgetView.php new file mode 100644 index 0000000..ccf3f05 --- /dev/null +++ b/project/cli/generate/views/newExampleProjectHeaderWidgetView.php @@ -0,0 +1,21 @@ + + + + '."\n"; + } + ?> + + + + + + + <?php echo $title; ?> + '."\n"; + } + ?> + \ No newline at end of file diff --git a/project/cli/generate/views/newExampleProjectIndexPage.php b/project/cli/generate/views/newExampleProjectIndexPage.php new file mode 100644 index 0000000..4b598ad --- /dev/null +++ b/project/cli/generate/views/newExampleProjectIndexPage.php @@ -0,0 +1,24 @@ + + * @version SVN: $id + * @package pages + * @subpackage index + */ +class Index extends \project\apps\{{ projectName }}\templates\PageTemplate { + + /** + * Function - Execute + * @param \nano\core\routing\Routing $routing Routing + */ + public function execute(\nano\core\routing\Routing $routing){ + $this->headerWidget->setTitle('Example Index Page'); + return 'project/apps/{{ projectName }}/pages/index/views/index.twig'; + } +} \ No newline at end of file diff --git a/project/cli/generate/views/newExampleProjectIndexPageView.php b/project/cli/generate/views/newExampleProjectIndexPageView.php new file mode 100644 index 0000000..81db7f4 --- /dev/null +++ b/project/cli/generate/views/newExampleProjectIndexPageView.php @@ -0,0 +1,9 @@ +{% extends "project/apps/{{ projectName }}/layouts/layout.twig" %} + +{% block content_container %} +
    + + Looks like everything is working!! :) + +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/cli/generate/views/newExampleProjectTemplateClass.php b/project/cli/generate/views/newExampleProjectTemplateClass.php new file mode 100644 index 0000000..ca866ca --- /dev/null +++ b/project/cli/generate/views/newExampleProjectTemplateClass.php @@ -0,0 +1,37 @@ + + * @version SVN: $id + * @package {{ projectName }} + * @subpackage templates + */ +class PageTemplate extends \nano\core\page\Page { + + protected $headerWidget = null; + + /** + * Function - Pre Execute + */ + protected function preExecute(\nano\core\routing\Routing $routing) { + $this->headerWidget = new \project\apps\{{ projectName }}\widgets\header\Header(); + $this->headerWidget->addStyleSheet('/css/globals/default.css'); + $this->headerWidget->addStyleSheet('/css/globals/colorbox-default.css'); + $this->headerWidget->addJavaScript('/js/globals/jquery.min.js'); + $this->headerWidget->addJavaScript('/js/globals/jquery-ui.min.js'); + $this->headerWidget->addJavaScript('/js/globals/jquery.colorbox-min.js'); + $this->headerWidget->addJavaScript('/js/globals/core.js'); + } + + /** + * Function - Post Execute + */ + protected function postExecute(\nano\core\routing\Routing $routing) { + $this->header = $this->headerWidget->getRenderedWidget(); + } +} \ No newline at end of file diff --git a/project/cli/generate/views/newExampleProjectTwigLayout.php b/project/cli/generate/views/newExampleProjectTwigLayout.php new file mode 100644 index 0000000..af79573 --- /dev/null +++ b/project/cli/generate/views/newExampleProjectTwigLayout.php @@ -0,0 +1,25 @@ +{% block header %} + {{ header }} +{% endblock header %} + + {% block body %} + +
    + {% endblock %} + + {% block content_container %} +
     
    + {% endblock content_container %} + + {% block end_body %} +
    + {% endblock %} + + {% block log_bar %} + {{ 'log' | inc }} + {% endblock log_bar %} + +{% block footer %} + + +{% endblock footer %} \ No newline at end of file diff --git a/project/cli/generate/views/newMap.php b/project/cli/generate/views/newMap.php new file mode 100644 index 0000000..5c57174 --- /dev/null +++ b/project/cli/generate/views/newMap.php @@ -0,0 +1,32 @@ + + * @version SVN: $id + * @package {{ database }} + * @subpackage templates + */ +class Map { +{{ classBody }} + public static function getModelNameFromTableName($tableName){ + return isset(self::$tableNameToModelName[$tableName])? self::$tableNameToModelName[$tableName] : null; + } + + public static function getTableNameFromModelName($modelName){ + return isset(self::$modelNameToTableName[$modelName])? self::$modelNameToTableName[$modelName] : null; + } +} \ No newline at end of file diff --git a/project/cli/generate/views/newModel.php b/project/cli/generate/views/newModel.php new file mode 100644 index 0000000..a653c5b --- /dev/null +++ b/project/cli/generate/views/newModel.php @@ -0,0 +1,16 @@ + + * @version SVN: $id + * @package om + * @subpackage {{ database }} + */ +class {{ modelName }} extends \project\db\om\{{ database }}\base\Base{{ modelName }} { +{{ classBody }} +} \ No newline at end of file diff --git a/project/cli/generate/views/newModelTable.php b/project/cli/generate/views/newModelTable.php new file mode 100644 index 0000000..8e6b9f6 --- /dev/null +++ b/project/cli/generate/views/newModelTable.php @@ -0,0 +1,16 @@ + + * @version SVN: $id + * @package om + * @subpackage {{ database }} + */ +class {{ modelName }}Table extends \project\db\om\{{ database }}\base\Base{{ modelName }}Table { +{{ classBody }} +} \ No newline at end of file diff --git a/project/cli/generate/views/newProjectConfig.php b/project/cli/generate/views/newProjectConfig.php new file mode 100644 index 0000000..34fcf1a --- /dev/null +++ b/project/cli/generate/views/newProjectConfig.php @@ -0,0 +1,23 @@ + + * @version SVN: $id + * @package projects + * @subpackage {{ projectName }} + */ +class Config extends \project\config\Config { + + /** + * Function - Boot Strap + */ + public static function bootStrap(){ + + } + +} \ No newline at end of file diff --git a/project/cli/generate/views/newProjectRouting.php b/project/cli/generate/views/newProjectRouting.php new file mode 100644 index 0000000..c7bc957 --- /dev/null +++ b/project/cli/generate/views/newProjectRouting.php @@ -0,0 +1,36 @@ + + * @version SVN: $id + * @package projects + * @subpackage {{ projectName }} + */ +class Routing { + + public static $routing = array( + /* + ROUTING FOR PROJECT '{{ projectName }}' + */ + // 'example_page' => array // 'example_page' - this should be a unique name. Use prefixes :) + // ( + // 'url' => '/example_page', //This is the url to match, must start with '/' | Slugs = /:bla/ | Otherwise regex format + // //Slugs appear in the $_GET global var. + // 'requirements' => array( //Matches the slugs provided in the URL (regex format) (NOT REQUIRED ATTRIBUTE) + // 'id' => '\d+', + // 'type' => '\w+' + // ), + // 'class' => 'project\apps\{{ projectName }}\Example' //The class to load if a match... Will not continue to next rule. + // ) + 'index_page' => array // 'example_page' - this should be a unique name. Use prefixes :) + ( + 'url' => '[/]?', + 'class' => 'project\apps\{{ projectName }}\pages\index\Index' //The class to load if a match... Will not continue to next rule. + ) + ); +} \ No newline at end of file diff --git a/project/cli/generate/views/newTask.php b/project/cli/generate/views/newTask.php new file mode 100644 index 0000000..c6b6cc7 --- /dev/null +++ b/project/cli/generate/views/newTask.php @@ -0,0 +1,23 @@ + + * @version SVN: $id + * @package cli + * @subpackage task + */ +class {{ className }} extends \nano\core\cli\TaskBase { + + /** + * Function - Execute + */ + public function execute() { + echo 'Your code here...'."\n"; + } + +} \ No newline at end of file diff --git a/project/cli/generate/views/newUnitBootStrap.php b/project/cli/generate/views/newUnitBootStrap.php new file mode 100644 index 0000000..1ff366b --- /dev/null +++ b/project/cli/generate/views/newUnitBootStrap.php @@ -0,0 +1,2 @@ + + + + project/apps/{{ projectName }}/tests/unit/lib + + + \ No newline at end of file diff --git a/project/cli/generate/views/newUnitTestExample.php b/project/cli/generate/views/newUnitTestExample.php new file mode 100644 index 0000000..233a7a4 --- /dev/null +++ b/project/cli/generate/views/newUnitTestExample.php @@ -0,0 +1,28 @@ + + * @version SVN: $id + */ +class Example extends \nano\core\test\Unit +{ + /** + * Function - Test Push And Pop + */ + public function testPushAndPop() + { + $stack = array(); + $this->assertEquals(0, count($stack)); + + array_push($stack, 'foo'); + $this->assertEquals('foo', $stack[count($stack)-1]); + $this->assertEquals(1, count($stack)); + + $this->assertEquals('foo', array_pop($stack)); + $this->assertEquals(0, count($stack)); + } +} \ No newline at end of file diff --git a/project/cli/task/RunUnitTests.class.php b/project/cli/task/RunUnitTests.class.php new file mode 100644 index 0000000..701efae --- /dev/null +++ b/project/cli/task/RunUnitTests.class.php @@ -0,0 +1,41 @@ + array( + 'include_path' => 'lib/Twig-1.0.0-RC1/lib/Twig/Autoloader.php', + 'class_name' => 'Twig_Autoloader', + 'call_function_name' => 'register', + 'call_function_parameters' => array(), + 'call_type' => 'static', + 'config_class_name' => 'project\lib\twig\Twig', + 'config_call_function_name' => 'configure', + 'config_call_function_parameters' => array(), + 'config_call_type' => 'static' + ), + 'Swift' => array( + 'include_path' => 'lib/Swift/lib/classes/Swift.php', + 'class_name' => 'Swift', + 'call_function_name' => 'registerAutoload', + 'call_function_parameters' => array(), + 'call_type' => 'static', + 'config_class_name' => 'project\lib\swift\Swift', + 'config_call_function_name' => 'configure', + 'config_call_function_parameters' => array(), + 'config_call_type' => 'static' + ) + ); + + public static $language = array( + 'ca_ES' => array( + 'name' => 'Catalan', + 'encoding' => 'UTF-8' + ), + 'cs_CZ' => array( + 'name' => 'Czech', + 'encoding' => 'UTF-8' + ), + 'cy_GB' => array( + 'name' => 'Welsh', + 'encoding' => 'UTF-8' + ), + 'da_DK' => array( + 'name' => 'Danish', + 'encoding' => 'UTF-8' + ), + 'de_DE' => array( + 'name' => 'German', + 'encoding' => 'UTF-8' + ), + 'eu_ES' => array( + 'name' => 'Basque', + 'encoding' => 'UTF-8' + ), + 'en_PI' => array( + 'name' => 'English (Pirate)', + 'encoding' => 'UTF-8' + ), + 'en_UD' => array( + 'name' => 'English (Upside Down)', + 'encoding' => 'UTF-8' + ), + 'ck_US' => array( + 'name' => 'Cherokee', + 'encoding' => 'UTF-8' + ), + 'en_US' => array( + 'name' => 'English (US)', + 'encoding' => 'UTF-8' + ), + 'es_LA' => array( + 'name' => 'Spanish', + 'encoding' => 'UTF-8' + ), + 'es_CL' => array( + 'name' => 'Spanish (Chile)', + 'encoding' => 'UTF-8' + ), + 'es_CO' => array( + 'name' => 'Spanish (Colombia)', + 'encoding' => 'UTF-8' + ), + 'es_ES' => array( + 'name' => 'Spanish (Spain)', + 'encoding' => 'UTF-8' + ), + 'es_MX' => array( + 'name' => 'Spanish (Mexico)', + 'encoding' => 'UTF-8' + ), + 'es_VE' => array( + 'name' => 'Spanish (Venezuela)', + 'encoding' => 'UTF-8' + ), + 'fb_FI' => array( + 'name' => 'Finnish (test)', + 'encoding' => 'UTF-8' + ), + 'fi_FI' => array( + 'name' => 'Finnish', + 'encoding' => 'UTF-8' + ), + 'fr_FR' => array( + 'name' => 'French (France)', + 'encoding' => 'UTF-8' + ), + 'gl_ES' => array( + 'name' => 'Galician', + 'encoding' => 'UTF-8' + ), + 'hu_HU' => array( + 'name' => 'Hungarian', + 'encoding' => 'UTF-8' + ), + 'it_IT' => array( + 'name' => 'Italian', + 'encoding' => 'UTF-8' + ), + 'ja_JP' => array( + 'name' => 'Japanese', + 'encoding' => 'UTF-8' + ), + 'ko_KR' => array( + 'name' => 'Korean', + 'encoding' => 'UTF-8' + ), + 'nb_NO' => array( + 'name' => 'Norwegian (bokmal)', + 'encoding' => 'UTF-8' + ), + 'nn_NO' => array( + 'name' => 'Norwegian (nynorsk)', + 'encoding' => 'UTF-8' + ), + 'nl_NL' => array( + 'name' => 'Dutch', + 'encoding' => 'UTF-8' + ), + 'pl_PL' => array( + 'name' => 'Polish', + 'encoding' => 'UTF-8' + ), + 'pt_BR' => array( + 'name' => 'Portuguese (Brazil)', + 'encoding' => 'UTF-8' + ), + 'pt_PT' => array( + 'name' => 'Portuguese (Portugal)', + 'encoding' => 'UTF-8' + ), + 'ro_RO' => array( + 'name' => 'Romanian', + 'encoding' => 'UTF-8' + ), + 'ru_RU' => array( + 'name' => 'Russian', + 'encoding' => 'UTF-8' + ), + 'sk_SK' => array( + 'name' => 'Slovak', + 'encoding' => 'UTF-8' + ), + 'sl_SI' => array( + 'name' => 'Slovenian', + 'encoding' => 'UTF-8' + ), + 'sv_SE' => array( + 'name' => 'Swedish', + 'encoding' => 'UTF-8' + ), + 'th_TH' => array( + 'name' => 'Thai', + 'encoding' => 'UTF-8' + ), + 'tr_TR' => array( + 'name' => 'Turkish', + 'encoding' => 'UTF-8' + ), + 'ku_TR' => array( + 'name' => 'Kurdish', + 'encoding' => 'UTF-8' + ), + 'zh_CN' => array( + 'name' => 'Simplified Chinese (China)', + 'encoding' => 'UTF-8' + ), + 'zh_HK' => array( + 'name' => 'Traditional Chinese (Hong Kong)', + 'encoding' => 'UTF-8' + ), + 'zh_TW' => array( + 'name' => 'Traditional Chinese (Taiwan)', + 'encoding' => 'UTF-8' + ), + 'fb_LT' => array( + 'name' => 'Leet Speak', + 'encoding' => 'UTF-8' + ), + 'af_ZA' => array( + 'name' => 'Afrikaans', + 'encoding' => 'UTF-8' + ), + 'sq_AL' => array( + 'name' => 'Albanian', + 'encoding' => 'UTF-8' + ), + 'hy_AM' => array( + 'name' => 'Armenian', + 'encoding' => 'UTF-8' + ), + 'az_AZ' => array( + 'name' => 'Azeri', + 'encoding' => 'UTF-8' + ), + 'be_BY' => array( + 'name' => 'Belarusian', + 'encoding' => 'UTF-8' + ), + 'bn_IN' => array( + 'name' => 'Bengali', + 'encoding' => 'UTF-8' + ), + 'bs_BA' => array( + 'name' => 'Bosnian', + 'encoding' => 'UTF-8' + ), + 'bg_BG' => array( + 'name' => 'Bulgarian', + 'encoding' => 'UTF-8' + ), + 'hr_HR' => array( + 'name' => 'Croatian', + 'encoding' => 'UTF-8' + ), + 'nl_BE' => array( + 'name' => 'Dutch (België)', + 'encoding' => 'UTF-8' + ), + 'en_GB' => array( + 'name' => 'English (UK)', + 'encoding' => 'UTF-8' + ), + 'eo_EO' => array( + 'name' => 'Esperanto', + 'encoding' => 'UTF-8' + ), + 'et_EE' => array( + 'name' => 'Estonian', + 'encoding' => 'UTF-8' + ), + 'fo_FO' => array( + 'name' => 'Faroese', + 'encoding' => 'UTF-8' + ), + 'fr_CA' => array( + 'name' => 'French (Canada)', + 'encoding' => 'UTF-8' + ), + 'ka_GE' => array( + 'name' => 'Georgian', + 'encoding' => 'UTF-8' + ), + 'el_GR' => array( + 'name' => 'Greek', + 'encoding' => 'UTF-8' + ), + 'gu_IN' => array( + 'name' => 'Gujarati', + 'encoding' => 'UTF-8' + ), + 'hi_IN' => array( + 'name' => 'Hindi', + 'encoding' => 'UTF-8' + ), + 'is_IS' => array( + 'name' => 'Icelandic', + 'encoding' => 'UTF-8' + ), + 'id_ID' => array( + 'name' => 'Indonesian', + 'encoding' => 'UTF-8' + ), + 'ga_IE' => array( + 'name' => 'Irish', + 'encoding' => 'UTF-8' + ), + 'jv_ID' => array( + 'name' => 'Javanese', + 'encoding' => 'UTF-8' + ), + 'kn_IN' => array( + 'name' => 'Kannada', + 'encoding' => 'UTF-8' + ), + 'kk_KZ' => array( + 'name' => 'Kazakh', + 'encoding' => 'UTF-8' + ), + 'la_VA' => array( + 'name' => 'Latin', + 'encoding' => 'UTF-8' + ), + 'lv_LV' => array( + 'name' => 'Latvian', + 'encoding' => 'UTF-8' + ), + 'li_NL' => array( + 'name' => 'Limburgish', + 'encoding' => 'UTF-8' + ), + 'lt_LT' => array( + 'name' => 'Lithuanian', + 'encoding' => 'UTF-8' + ), + 'mk_MK' => array( + 'name' => 'Macedonian', + 'encoding' => 'UTF-8' + ), + 'mg_MG' => array( + 'name' => 'Malagasy', + 'encoding' => 'UTF-8' + ), + 'ms_MY' => array( + 'name' => 'Malay', + 'encoding' => 'UTF-8' + ), + 'mt_MT' => array( + 'name' => 'Maltese', + 'encoding' => 'UTF-8' + ), + 'mr_IN' => array( + 'name' => 'Marathi', + 'encoding' => 'UTF-8' + ), + 'mn_MN' => array( + 'name' => 'Mongolian', + 'encoding' => 'UTF-8' + ), + 'ne_NP' => array( + 'name' => 'Nepali', + 'encoding' => 'UTF-8' + ), + 'pa_IN' => array( + 'name' => 'Punjabi', + 'encoding' => 'UTF-8' + ), + 'rm_CH' => array( + 'name' => 'Romansh', + 'encoding' => 'UTF-8' + ), + 'sa_IN' => array( + 'name' => 'Sanskrit', + 'encoding' => 'UTF-8' + ), + 'sr_RS' => array( + 'name' => 'Serbian', + 'encoding' => 'UTF-8' + ), + 'so_SO' => array( + 'name' => 'Somali', + 'encoding' => 'UTF-8' + ), + 'sw_KE' => array( + 'name' => 'Swahili', + 'encoding' => 'UTF-8' + ), + 'tl_PH' => array( + 'name' => 'Filipino', + 'encoding' => 'UTF-8' + ), + 'ta_IN' => array( + 'name' => 'Tamil', + 'encoding' => 'UTF-8' + ), + 'tt_RU' => array( + 'name' => 'Tatar', + 'encoding' => 'UTF-8' + ), + 'te_IN' => array( + 'name' => 'Telugu', + 'encoding' => 'UTF-8' + ), + 'ml_IN' => array( + 'name' => 'Malayalam', + 'encoding' => 'UTF-8' + ), + 'uk_UA' => array( + 'name' => 'Ukrainian', + 'encoding' => 'UTF-8' + ), + 'uz_UZ' => array( + 'name' => 'Uzbek', + 'encoding' => 'UTF-8' + ), + 'vi_VN' => array( + 'name' => 'Vietnamese', + 'encoding' => 'UTF-8' + ), + 'xh_ZA' => array( + 'name' => 'Xhosa', + 'encoding' => 'UTF-8' + ), + 'zu_ZA' => array( + 'name' => 'Zulu', + 'encoding' => 'UTF-8' + ), + 'km_KH' => array( + 'name' => 'Khmer', + 'encoding' => 'UTF-8' + ), + 'tg_TJ' => array( + 'name' => 'Tajik', + 'encoding' => 'UTF-8' + ), + 'ar_AR' => array( + 'name' => 'Arabic', + 'encoding' => 'UTF-8' + ), + 'he_IL' => array( + 'name' => 'Hebrew', + 'encoding' => 'UTF-8' + ), + 'ur_PK' => array( + 'name' => 'Urdu', + 'encoding' => 'UTF-8' + ), + 'fa_IR' => array( + 'name' => 'Persian', + 'encoding' => 'UTF-8' + ), + 'sy_SY' => array( + 'name' => 'Syriac', + 'encoding' => 'UTF-8' + ), + 'yi_DE' => array( + 'name' => 'Yiddish', + 'encoding' => 'UTF-8' + ), + 'gn_PY' => array( + 'name' => 'Guaraní', + 'encoding' => 'UTF-8' + ), + 'qu_PE' => array( + 'name' => 'Quechua', + 'encoding' => 'UTF-8' + ), + 'ay_BO' => array( + 'name' => 'Aymara', + 'encoding' => 'UTF-8' + ), + 'se_NO' => array( + 'name' => 'Northern Sámi', + 'encoding' => 'UTF-8' + ), + 'ps_AF' => array( + 'name' => 'Pashto', + 'encoding' => 'UTF-8' + ), + 'tl_ST' => array( + 'name' => 'Klingon', + 'encoding' => 'UTF-8' + ) + ); + +} diff --git a/project/config/PHPErrors.class.php b/project/config/PHPErrors.class.php new file mode 100644 index 0000000..7ef448a --- /dev/null +++ b/project/config/PHPErrors.class.php @@ -0,0 +1,6 @@ + 'nanophp', /** Theme */ + + /* Twig Settings - See Twig documentation for details. */ + 'twig_debug' => true, + 'twig_auto_reload' => true, + + /* Internationalization Settings */ + 'i18n_populate' => true, /* If true, any i18n values specified in the i18n() call will be added to the database if they do not exist. */ + 'i18n_overwrite' => false, /* If true, any i18n values specified in the i18n() call will replace existing values in the database. */ + 'i18n_marker' => false, /* If true, {{ double braces }} will be placed around any text returned by the i18n() function. */ + + /* Log Settings */ + 'logging_bar' => true, /* Show the logging bar at the top of the page for development purposes. */ + 'logging_priority' => \nano\core\log\Log::PRIORITY_LEVEL_LOGFILE, /* Default priority level for logging errors. Can be changed in individual exception classes for added control */ + 'logging_send_to' => array( + /* Errors with priority PRIORITY_LEVEL_EMAIL will be sent to all addresses in this array. */ + 'yourname@yourdomain.com', + ), + + /* Database Settings */ + 'databases' => array ( + /* 'default' database configuration must exist; others may be added below. */ + 'default' => array( + 'mode' => \nano\core\db\core\Database::DB_MODE_SINGLE, /* Database access mode. */ + 'name' => 'nanophp', /* MySQL database name. */ + 'servers' => array( + array( + 'host' => 'localhost', /* Database hostname/IP address. */ + 'port' => '3306', /* Database port. UNIX sockets are not supported at this time. */ + 'user' => 'root', /* MySQL username. */ + 'pass' => 'test' /* MySQL password. */ + ) + ) + ) + ), + + /* Memcache Settings */ + 'caching' => true, /* Set to TRUE to enable the cache. The PHP 'Memcached' extension, and memcached itself, must be installed. */ + 'memcache_servers' => array( + /* Array of memcached servers in the form 'hostname' => 'port'. */ + 'default' => array( + '127.0.0.1' => '11211' + ), + ), + + /* Configure PHP's error handling functionality according to one of the functions defined in PHPErrors.class.php. */ + 'errors' => 'dev' + ); +} \ No newline at end of file diff --git a/project/db/om/nanophp/Auth.class.php b/project/db/om/nanophp/Auth.class.php new file mode 100644 index 0000000..727f92e --- /dev/null +++ b/project/db/om/nanophp/Auth.class.php @@ -0,0 +1,16 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class Auth extends \project\db\om\nanophp\base\BaseAuth { + +} \ No newline at end of file diff --git a/project/db/om/nanophp/AuthTable.class.php b/project/db/om/nanophp/AuthTable.class.php new file mode 100644 index 0000000..0fa149d --- /dev/null +++ b/project/db/om/nanophp/AuthTable.class.php @@ -0,0 +1,30 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class AuthTable extends \project\db\om\nanophp\base\BaseAuthTable { + + public function retrieveByTypeAndUid($type,$uid) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Auths')->where('`Auths`.`type` = "'.addslashes($type).'" AND `Auths`.`uid` = "'.addslashes($uid).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['Auth'])? $results[0]['Auth'] : null; + } + + public function retrieveByTypeAndFuid($type,$fuid) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Auths')->where('`Auths`.`type` = "'.addslashes($type).'" AND `Auths`.`fuid` = "'.addslashes($fuid).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['Auth'])? $results[0]['Auth'] : null; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/Language.class.php b/project/db/om/nanophp/Language.class.php new file mode 100644 index 0000000..fdbf4a0 --- /dev/null +++ b/project/db/om/nanophp/Language.class.php @@ -0,0 +1,38 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class Language extends \project\db\om\nanophp\base\BaseLanguage { + + /** + * Function - Validate Code + * @param mixed $value Value + */ + public static function validateCode($value){ + if(preg_match('/^[a-z]{2,3}\_[A-Z]{2,3}$/',$value)){ + return true; + } + return false; + } + + /** + * Function - Save + */ + public function save(){ + if(\nano\core\config\Config::get('caching')==true){ + $memcached = \nano\core\cache\Memcached::getInstance(); + $memcached->flush(); + } + parent::save(); + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/LanguageTable.class.php b/project/db/om/nanophp/LanguageTable.class.php new file mode 100644 index 0000000..dad4742 --- /dev/null +++ b/project/db/om/nanophp/LanguageTable.class.php @@ -0,0 +1,16 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class LanguageTable extends \project\db\om\nanophp\base\BaseLanguageTable { + +} \ No newline at end of file diff --git a/project/db/om/nanophp/Session.class.php b/project/db/om/nanophp/Session.class.php new file mode 100644 index 0000000..14cccf0 --- /dev/null +++ b/project/db/om/nanophp/Session.class.php @@ -0,0 +1,36 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class Session extends \project\db\om\nanophp\base\BaseSession { + + /** + * Function - Set Ip + * @param mixed $value Value + */ + public function setIp($value){ + if(\project\db\om\nanophp\Session::validateIp(ip2long($value))){ + $this->ip = ip2long($value); + } else { + throw new \nano\core\exception\ValidationException('Validation of column `ip` failed'); + } + } + + /** + * Function - Get Ip + * @return mixed + */ + public function getIp(){ + return long2ip($this->ip); + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/SessionTable.class.php b/project/db/om/nanophp/SessionTable.class.php new file mode 100644 index 0000000..0e04797 --- /dev/null +++ b/project/db/om/nanophp/SessionTable.class.php @@ -0,0 +1,41 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class SessionTable extends \project\db\om\nanophp\base\BaseSessionTable { + + /** + * Function - Retrieve By User + * @param mixed $user User + * @return mixed + */ + public function retrieveByUser($user) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Sessions')->where('`Sessions`.`User` = "'.addslashes($user).'" LIMIT 1'); + $results = $this->doSelect($query,'session-'.$user); + return isset($results[0]['Session'])? $results[0]['Session'] : null; + } + + /** + * Function - Retrieve By User And Token + * @param mixed $user User + * @param mixed $token Token + * @return mixed + */ + public function retrieveByUserAndToken($user,$token) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Sessions')->where('`Sessions`.`token` = "'.addslashes($token).'" AND `Sessions`.`User` = "'.addslashes($user).'" LIMIT 1'); + $results = $this->doSelect($query,'session-'.$user.'-'.$token); + return isset($results[0]['Session'])? $results[0]['Session'] : null; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/User.class.php b/project/db/om/nanophp/User.class.php new file mode 100644 index 0000000..9d65da0 --- /dev/null +++ b/project/db/om/nanophp/User.class.php @@ -0,0 +1,16 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class User extends \project\db\om\nanophp\base\BaseUser { + +} \ No newline at end of file diff --git a/project/db/om/nanophp/UserTable.class.php b/project/db/om/nanophp/UserTable.class.php new file mode 100644 index 0000000..eb495fd --- /dev/null +++ b/project/db/om/nanophp/UserTable.class.php @@ -0,0 +1,53 @@ + + * @version SVN: $id + * @package om + * @subpackage nanophp + */ +class UserTable extends \project\db\om\nanophp\base\BaseUserTable { + + /** + * Function - Retrieve By Pk + * @param mixed $id Id + * @return mixed + */ + public function retrieveByPk($id) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Users')->where('`Users`.`id` = "'.addslashes($id).'" LIMIT 1'); + $results = $this->doSelect($query,'user-'.$id); + return isset($results[0]['User'])? $results[0]['User'] : null; + } + + /** + * Function - Retrieve By Email + * @param mixed $email Email + * @return mixed + */ + public function retrieveByEmail($email) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Users')->where('`Users`.`email` = "'.addslashes($email).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['User'])? $results[0]['User'] : null; + } + + /** + * Function - Retrieve By Email And Password + * @param mixed $email Email + * @param mixed $password Password + * @return mixed + */ + public function retrieveByEmailAndPassword($email,$password) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Users')->where('`Users`.`is_active` = 1 AND `Users`.`password` = "'.addslashes($password).'" AND `Users`.`email` = "'.addslashes($email).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['User'])? $results[0]['User'] : null; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseAuth.class.php b/project/db/om/nanophp/base/BaseAuth.class.php new file mode 100644 index 0000000..fe7b06b --- /dev/null +++ b/project/db/om/nanophp/base/BaseAuth.class.php @@ -0,0 +1,175 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseAuth extends \nano\core\db\om\Base { + + protected $modelName = 'Auth'; + protected $primaryKey = array('id'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Auths'; + protected $fields = array( + 'id' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => 'auto_increment', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setId', + 'validation_function' => 'validateId', + 'get_function' => 'getId', + ), + 'type' => array( + 'mysql_type' => 'varchar(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setType', + 'validation_function' => 'validateType', + 'get_function' => 'getType', + ), + 'uid' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setUid', + 'validation_function' => 'validateUid', + 'get_function' => 'getUid', + ), + 'fuid' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'MUL', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setFuid', + 'validation_function' => 'validateFuid', + 'get_function' => 'getFuid', + ), + 'perams' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setPerams', + 'validation_function' => 'validatePerams', + 'get_function' => 'getPerams', + ) + ); + protected $newFieldNameMap = array( + 'id' => 'id', + 'type' => 'type', + 'uid' => 'uid', + 'fuid' => 'fuid', + 'perams' => 'perams' + ); + public function setId($value){ + if(\project\db\om\nanophp\Auth::validateId($value)){ + $this->id = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `id` failed'); + } + } + public function setType($value){ + if(\project\db\om\nanophp\Auth::validateType($value)){ + $this->type = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `type` failed'); + } + } + public function setUid($value){ + if(\project\db\om\nanophp\Auth::validateUid($value)){ + $this->uid = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `uid` failed'); + } + } + public function setFuid($value){ + if(\project\db\om\nanophp\Auth::validateFuid($value)){ + $this->fuid = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `fuid` failed'); + } + } + public function setPerams($value){ + if(\project\db\om\nanophp\Auth::validatePerams($value)){ + $this->perams = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `perams` failed'); + } + } + public function getId(){ + return $this->id; + } + public function getType(){ + return $this->type; + } + public function getUid(){ + return $this->uid; + } + public function getFuid(){ + return $this->fuid; + } + public function getPerams(){ + return $this->perams; + } + public static function validateId($value){ + return true; + } + public static function validateType($value){ + return true; + } + public static function validateUid($value){ + return true; + } + public static function validateFuid($value){ + return true; + } + public static function validatePerams($value){ + return true; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseAuthTable.class.php b/project/db/om/nanophp/base/BaseAuthTable.class.php new file mode 100644 index 0000000..5f51c75 --- /dev/null +++ b/project/db/om/nanophp/base/BaseAuthTable.class.php @@ -0,0 +1,117 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseAuthTable extends \nano\core\db\om\BaseTable { + + protected $modelName = 'Auth'; + protected $primaryKey = array('id'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Auths'; + protected $fields = array( + 'id' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => 'auto_increment', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setId', + 'validation_function' => 'validateId', + 'get_function' => 'getId', + ), + 'type' => array( + 'mysql_type' => 'varchar(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setType', + 'validation_function' => 'validateType', + 'get_function' => 'getType', + ), + 'uid' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setUid', + 'validation_function' => 'validateUid', + 'get_function' => 'getUid', + ), + 'fuid' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'MUL', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setFuid', + 'validation_function' => 'validateFuid', + 'get_function' => 'getFuid', + ), + 'perams' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Auth', + 'use_database' => 'nanophp', + 'set_function' => 'setPerams', + 'validation_function' => 'validatePerams', + 'get_function' => 'getPerams', + ) + ); + protected $newFieldNameMap = array( + 'id' => 'id', + 'type' => 'type', + 'uid' => 'uid', + 'fuid' => 'fuid', + 'perams' => 'perams' + ); + + public function retrieveByPk($id) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Auths')->where('`Auths`.`id` = "'.addslashes($id).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['Auth'])? $results[0]['Auth'] : null; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseLanguage.class.php b/project/db/om/nanophp/base/BaseLanguage.class.php new file mode 100644 index 0000000..d448113 --- /dev/null +++ b/project/db/om/nanophp/base/BaseLanguage.class.php @@ -0,0 +1,121 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseLanguage extends \nano\core\db\om\Base { + + protected $modelName = 'Language'; + protected $primaryKey = array('code','key'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Languages'; + protected $fields = array( + 'code' => array( + 'mysql_type' => 'char(7)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Language', + 'use_database' => 'nanophp', + 'set_function' => 'setCode', + 'validation_function' => 'validateCode', + 'get_function' => 'getCode', + ), + 'key' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Language', + 'use_database' => 'nanophp', + 'set_function' => 'setKey', + 'validation_function' => 'validateKey', + 'get_function' => 'getKey', + ), + 'value' => array( + 'mysql_type' => 'text', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Language', + 'use_database' => 'nanophp', + 'set_function' => 'setValue', + 'validation_function' => 'validateValue', + 'get_function' => 'getValue', + ) + ); + protected $newFieldNameMap = array( + 'code' => 'code', + 'key' => 'key', + 'value' => 'value' + ); + public function setCode($value){ + if(\project\db\om\nanophp\Language::validateCode($value)){ + $this->code = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `code` failed'); + } + } + public function setKey($value){ + if(\project\db\om\nanophp\Language::validateKey($value)){ + $this->key = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `key` failed'); + } + } + public function setValue($value){ + if(\project\db\om\nanophp\Language::validateValue($value)){ + $this->value = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `value` failed'); + } + } + public function getCode(){ + return $this->code; + } + public function getKey(){ + return $this->key; + } + public function getValue(){ + return $this->value; + } + public static function validateCode($value){ + return true; + } + public static function validateKey($value){ + return true; + } + public static function validateValue($value){ + return true; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseLanguageTable.class.php b/project/db/om/nanophp/base/BaseLanguageTable.class.php new file mode 100644 index 0000000..e2ccca6 --- /dev/null +++ b/project/db/om/nanophp/base/BaseLanguageTable.class.php @@ -0,0 +1,89 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseLanguageTable extends \nano\core\db\om\BaseTable { + + protected $modelName = 'Language'; + protected $primaryKey = array('code','key'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Languages'; + protected $fields = array( + 'code' => array( + 'mysql_type' => 'char(7)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Language', + 'use_database' => 'nanophp', + 'set_function' => 'setCode', + 'validation_function' => 'validateCode', + 'get_function' => 'getCode', + ), + 'key' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Language', + 'use_database' => 'nanophp', + 'set_function' => 'setKey', + 'validation_function' => 'validateKey', + 'get_function' => 'getKey', + ), + 'value' => array( + 'mysql_type' => 'text', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Language', + 'use_database' => 'nanophp', + 'set_function' => 'setValue', + 'validation_function' => 'validateValue', + 'get_function' => 'getValue', + ) + ); + protected $newFieldNameMap = array( + 'code' => 'code', + 'key' => 'key', + 'value' => 'value' + ); + + public function retrieveByPk($code,$key) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Languages')->where('`Languages`.`code` = "'.addslashes($code).'" AND `Languages`.`key` = "'.addslashes($key).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['Language'])? $results[0]['Language'] : null; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseSession.class.php b/project/db/om/nanophp/base/BaseSession.class.php new file mode 100644 index 0000000..5b12f9a --- /dev/null +++ b/project/db/om/nanophp/base/BaseSession.class.php @@ -0,0 +1,175 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseSession extends \nano\core\db\om\Base { + + protected $modelName = 'Session'; + protected $primaryKey = array('id'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Sessions'; + protected $fields = array( + 'id' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => 'auto_increment', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setId', + 'validation_function' => 'validateId', + 'get_function' => 'getId', + ), + 'User' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => true, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setUser', + 'validation_function' => 'validateUser', + 'get_function' => 'getUser', + ), + 'token' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'MUL', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setToken', + 'validation_function' => 'validateToken', + 'get_function' => 'getToken', + ), + 'ip' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setIp', + 'validation_function' => 'validateIp', + 'get_function' => 'getIp', + ), + 'created' => array( + 'mysql_type' => 'datetime', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setCreated', + 'validation_function' => 'validateCreated', + 'get_function' => 'getCreated', + ) + ); + protected $newFieldNameMap = array( + 'id' => 'id', + 'User' => 'User', + 'token' => 'token', + 'ip' => 'ip', + 'created' => 'created' + ); + public function setId($value){ + if(\project\db\om\nanophp\Session::validateId($value)){ + $this->id = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `id` failed'); + } + } + public function setUser($value){ + if(\project\db\om\nanophp\Session::validateUser($value)){ + $this->User = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `User` failed'); + } + } + public function setToken($value){ + if(\project\db\om\nanophp\Session::validateToken($value)){ + $this->token = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `token` failed'); + } + } + public function setIp($value){ + if(\project\db\om\nanophp\Session::validateIp($value)){ + $this->ip = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `ip` failed'); + } + } + public function setCreated($value){ + if(\project\db\om\nanophp\Session::validateCreated($value)){ + $this->created = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `created` failed'); + } + } + public function getId(){ + return $this->id; + } + public function getUser(){ + return $this->User; + } + public function getToken(){ + return $this->token; + } + public function getIp(){ + return $this->ip; + } + public function getCreated(){ + return $this->created; + } + public static function validateId($value){ + return true; + } + public static function validateUser($value){ + return true; + } + public static function validateToken($value){ + return true; + } + public static function validateIp($value){ + return true; + } + public static function validateCreated($value){ + return true; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseSessionTable.class.php b/project/db/om/nanophp/base/BaseSessionTable.class.php new file mode 100644 index 0000000..434f3b0 --- /dev/null +++ b/project/db/om/nanophp/base/BaseSessionTable.class.php @@ -0,0 +1,117 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseSessionTable extends \nano\core\db\om\BaseTable { + + protected $modelName = 'Session'; + protected $primaryKey = array('id'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Sessions'; + protected $fields = array( + 'id' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => 'auto_increment', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setId', + 'validation_function' => 'validateId', + 'get_function' => 'getId', + ), + 'User' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => true, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setUser', + 'validation_function' => 'validateUser', + 'get_function' => 'getUser', + ), + 'token' => array( + 'mysql_type' => 'char(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'MUL', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setToken', + 'validation_function' => 'validateToken', + 'get_function' => 'getToken', + ), + 'ip' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setIp', + 'validation_function' => 'validateIp', + 'get_function' => 'getIp', + ), + 'created' => array( + 'mysql_type' => 'datetime', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'Session', + 'use_database' => 'nanophp', + 'set_function' => 'setCreated', + 'validation_function' => 'validateCreated', + 'get_function' => 'getCreated', + ) + ); + protected $newFieldNameMap = array( + 'id' => 'id', + 'User' => 'User', + 'token' => 'token', + 'ip' => 'ip', + 'created' => 'created' + ); + + public function retrieveByPk($id) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Sessions')->where('`Sessions`.`id` = "'.addslashes($id).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['Session'])? $results[0]['Session'] : null; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseUser.class.php b/project/db/om/nanophp/base/BaseUser.class.php new file mode 100644 index 0000000..c2ee248 --- /dev/null +++ b/project/db/om/nanophp/base/BaseUser.class.php @@ -0,0 +1,283 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseUser extends \nano\core\db\om\Base { + + protected $modelName = 'User'; + protected $primaryKey = array('id'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Users'; + protected $fields = array( + 'id' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => 'auto_increment', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setId', + 'validation_function' => 'validateId', + 'get_function' => 'getId', + ), + 'language' => array( + 'mysql_type' => 'char(7)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setLanguage', + 'validation_function' => 'validateLanguage', + 'get_function' => 'getLanguage', + ), + 'email' => array( + 'mysql_type' => 'varchar(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'MUL', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setEmail', + 'validation_function' => 'validateEmail', + 'get_function' => 'getEmail', + ), + 'password' => array( + 'mysql_type' => 'varchar(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setPassword', + 'validation_function' => 'validatePassword', + 'get_function' => 'getPassword', + ), + 'is_active' => array( + 'mysql_type' => 'tinyint(1)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '0', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setIsActive', + 'validation_function' => 'validateIsActive', + 'get_function' => 'getIsActive', + ), + 'is_admin' => array( + 'mysql_type' => 'tinyint(1)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '0', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setIsAdmin', + 'validation_function' => 'validateIsAdmin', + 'get_function' => 'getIsAdmin', + ), + 'is_super_admin' => array( + 'mysql_type' => 'tinyint(1)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '0', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setIsSuperAdmin', + 'validation_function' => 'validateIsSuperAdmin', + 'get_function' => 'getIsSuperAdmin', + ), + 'created_at' => array( + 'mysql_type' => 'datetime', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setCreatedAt', + 'validation_function' => 'validateCreatedAt', + 'get_function' => 'getCreatedAt', + ), + 'updated_at' => array( + 'mysql_type' => 'datetime', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setUpdatedAt', + 'validation_function' => 'validateUpdatedAt', + 'get_function' => 'getUpdatedAt', + ) + ); + protected $newFieldNameMap = array( + 'id' => 'id', + 'language' => 'language', + 'email' => 'email', + 'password' => 'password', + 'is_active' => 'is_active', + 'is_admin' => 'is_admin', + 'is_super_admin' => 'is_super_admin', + 'created_at' => 'created_at', + 'updated_at' => 'updated_at' + ); + public function setId($value){ + if(\project\db\om\nanophp\User::validateId($value)){ + $this->id = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `id` failed'); + } + } + public function setLanguage($value){ + if(\project\db\om\nanophp\User::validateLanguage($value)){ + $this->language = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `language` failed'); + } + } + public function setEmail($value){ + if(\project\db\om\nanophp\User::validateEmail($value)){ + $this->email = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `email` failed'); + } + } + public function setPassword($value){ + if(\project\db\om\nanophp\User::validatePassword($value)){ + $this->password = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `password` failed'); + } + } + public function setIsActive($value){ + if(\project\db\om\nanophp\User::validateIsActive($value)){ + $this->is_active = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `is_active` failed'); + } + } + public function setIsAdmin($value){ + if(\project\db\om\nanophp\User::validateIsAdmin($value)){ + $this->is_admin = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `is_admin` failed'); + } + } + public function setIsSuperAdmin($value){ + if(\project\db\om\nanophp\User::validateIsSuperAdmin($value)){ + $this->is_super_admin = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `is_super_admin` failed'); + } + } + public function setCreatedAt($value){ + if(\project\db\om\nanophp\User::validateCreatedAt($value)){ + $this->created_at = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `created_at` failed'); + } + } + public function setUpdatedAt($value){ + if(\project\db\om\nanophp\User::validateUpdatedAt($value)){ + $this->updated_at = $value; + } else { + throw new \nano\core\exception\ValidationException('Validation of column `updated_at` failed'); + } + } + public function getId(){ + return $this->id; + } + public function getLanguage(){ + return $this->language; + } + public function getEmail(){ + return $this->email; + } + public function getPassword(){ + return $this->password; + } + public function getIsActive(){ + return $this->is_active; + } + public function getIsAdmin(){ + return $this->is_admin; + } + public function getIsSuperAdmin(){ + return $this->is_super_admin; + } + public function getCreatedAt(){ + return $this->created_at; + } + public function getUpdatedAt(){ + return $this->updated_at; + } + public static function validateId($value){ + return true; + } + public static function validateLanguage($value){ + return true; + } + public static function validateEmail($value){ + return true; + } + public static function validatePassword($value){ + return true; + } + public static function validateIsActive($value){ + return true; + } + public static function validateIsAdmin($value){ + return true; + } + public static function validateIsSuperAdmin($value){ + return true; + } + public static function validateCreatedAt($value){ + return true; + } + public static function validateUpdatedAt($value){ + return true; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/base/BaseUserTable.class.php b/project/db/om/nanophp/base/BaseUserTable.class.php new file mode 100644 index 0000000..b1e328d --- /dev/null +++ b/project/db/om/nanophp/base/BaseUserTable.class.php @@ -0,0 +1,173 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage base + */ +class BaseUserTable extends \nano\core\db\om\BaseTable { + + protected $modelName = 'User'; + protected $primaryKey = array('id'); + protected $dbConfig = 'default'; + protected $dbName = 'nanophp'; + protected $tableName = 'Users'; + protected $fields = array( + 'id' => array( + 'mysql_type' => 'int(11)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'PRI', + 'mysql_default' => '', + 'mysql_extra' => 'auto_increment', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setId', + 'validation_function' => 'validateId', + 'get_function' => 'getId', + ), + 'language' => array( + 'mysql_type' => 'char(7)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setLanguage', + 'validation_function' => 'validateLanguage', + 'get_function' => 'getLanguage', + ), + 'email' => array( + 'mysql_type' => 'varchar(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => 'MUL', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setEmail', + 'validation_function' => 'validateEmail', + 'get_function' => 'getEmail', + ), + 'password' => array( + 'mysql_type' => 'varchar(255)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setPassword', + 'validation_function' => 'validatePassword', + 'get_function' => 'getPassword', + ), + 'is_active' => array( + 'mysql_type' => 'tinyint(1)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '0', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setIsActive', + 'validation_function' => 'validateIsActive', + 'get_function' => 'getIsActive', + ), + 'is_admin' => array( + 'mysql_type' => 'tinyint(1)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '0', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setIsAdmin', + 'validation_function' => 'validateIsAdmin', + 'get_function' => 'getIsAdmin', + ), + 'is_super_admin' => array( + 'mysql_type' => 'tinyint(1)', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '0', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setIsSuperAdmin', + 'validation_function' => 'validateIsSuperAdmin', + 'get_function' => 'getIsSuperAdmin', + ), + 'created_at' => array( + 'mysql_type' => 'datetime', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setCreatedAt', + 'validation_function' => 'validateCreatedAt', + 'get_function' => 'getCreatedAt', + ), + 'updated_at' => array( + 'mysql_type' => 'datetime', + 'mysql_is_null' => 'NO', + 'mysql_key' => '', + 'mysql_default' => '', + 'mysql_extra' => '', + 'is_foreign_reference' => false, + 'use_model' => 'User', + 'use_database' => 'nanophp', + 'set_function' => 'setUpdatedAt', + 'validation_function' => 'validateUpdatedAt', + 'get_function' => 'getUpdatedAt', + ) + ); + protected $newFieldNameMap = array( + 'id' => 'id', + 'language' => 'language', + 'email' => 'email', + 'password' => 'password', + 'is_active' => 'is_active', + 'is_admin' => 'is_admin', + 'is_super_admin' => 'is_super_admin', + 'created_at' => 'created_at', + 'updated_at' => 'updated_at' + ); + + public function retrieveByPk($id) { + $query = new \nano\core\db\core\SelectQuery(); + $query->from('Users')->where('`Users`.`id` = "'.addslashes($id).'" LIMIT 1'); + $results = $this->doSelect($query); + return isset($results[0]['User'])? $results[0]['User'] : null; + } + +} \ No newline at end of file diff --git a/project/db/om/nanophp/map/Map.class.php b/project/db/om/nanophp/map/Map.class.php new file mode 100644 index 0000000..02119b3 --- /dev/null +++ b/project/db/om/nanophp/map/Map.class.php @@ -0,0 +1,50 @@ + + * @version SVN: $id + * @package nanophp + * @subpackage templates + */ +class Map { + public static $tableNameToModelName = array( + 'Auths' => 'Auth', + 'Languages' => 'Language', + 'Sessions' => 'Session', + 'Users' => 'User' + ); + public static $modelNameToTableName = array( + 'Auth' => 'Auths', + 'Language' => 'Languages', + 'Session' => 'Sessions', + 'User' => 'Users' + ); + public static $mapTablesToColumns = array( + 'Auths' => array('id' => 'id','type' => 'type','uid' => 'uid','fuid' => 'fuid','perams' => 'perams'), + 'Languages' => array('code' => 'code','key' => 'key','value' => 'value'), + 'Sessions' => array('id' => 'id','User' => 'User','token' => 'token','ip' => 'ip','created' => 'created'), + 'Users' => array('id' => 'id','language' => 'language','email' => 'email','password' => 'password','is_active' => 'is_active','is_admin' => 'is_admin','is_super_admin' => 'is_super_admin','created_at' => 'created_at','updated_at' => 'updated_at') + ); + + public static function getModelNameFromTableName($tableName){ + return isset(self::$tableNameToModelName[$tableName])? self::$tableNameToModelName[$tableName] : null; + } + + public static function getTableNameFromModelName($modelName){ + return isset(self::$modelNameToTableName[$modelName])? self::$modelNameToTableName[$modelName] : null; + } +} \ No newline at end of file diff --git a/project/globals/layouts/layout.twig b/project/globals/layouts/layout.twig new file mode 100644 index 0000000..3303663 --- /dev/null +++ b/project/globals/layouts/layout.twig @@ -0,0 +1,25 @@ +{% block header %} + {{ header }} +{% endblock header %} + + {% block body %} + +
    + {% endblock body %} + + {% block content_container %} +
     
    + {% endblock content_container %} + + {% block end_body %} +
    + {% endblock end_body %} + + {% block log_bar %} + {{ 'log' | inc }} + {% endblock log_bar %} + +{% block footer %} + + +{% endblock footer %} \ No newline at end of file diff --git a/project/globals/pages/page_not_found/PageNotFound.class.php b/project/globals/pages/page_not_found/PageNotFound.class.php new file mode 100755 index 0000000..bdd626e --- /dev/null +++ b/project/globals/pages/page_not_found/PageNotFound.class.php @@ -0,0 +1,11 @@ +url = \nano\core\routing\Routing::getInstance()->getUrl(); + return 'project/globals/pages/page_not_found/views/page_not_found.twig'; + } +} diff --git a/project/globals/pages/page_not_found/views/page_not_found.twig b/project/globals/pages/page_not_found/views/page_not_found.twig new file mode 100644 index 0000000..9867941 --- /dev/null +++ b/project/globals/pages/page_not_found/views/page_not_found.twig @@ -0,0 +1,10 @@ +{% extends "project/globals/layouts/layout.twig" %} + +{% block content_container %} +
    +
    +

    Page Not Found

    +
    {{ url }}
    +
    +
    +{% endblock content_container %} \ No newline at end of file diff --git a/project/globals/partials/footer.php b/project/globals/partials/footer.php new file mode 100644 index 0000000..308b1d0 --- /dev/null +++ b/project/globals/partials/footer.php @@ -0,0 +1,2 @@ + + diff --git a/project/globals/partials/header.php b/project/globals/partials/header.php new file mode 100644 index 0000000..61d4ece --- /dev/null +++ b/project/globals/partials/header.php @@ -0,0 +1,21 @@ + + + + '."\n"); + } + ?> + + + + + + + <?php echo($title); ?> + '."\n"); + } + ?> + diff --git a/project/globals/templates/PageTemplate.class.php b/project/globals/templates/PageTemplate.class.php new file mode 100644 index 0000000..8ef808a --- /dev/null +++ b/project/globals/templates/PageTemplate.class.php @@ -0,0 +1,24 @@ +headerWidget = new \project\apps\back_end\widgets\header\Header(); + $this->headerWidget->addStyleSheet('/css/globals/admin.css'); + $this->headerWidget->addStyleSheet('/css/globals/default.css'); + $this->headerWidget->addStyleSheet('/css/globals/jquery/plugins/ui/'.\nano\core\config\Config::get('jquery_theme').'/jquery-ui-1.8.5.custom.css'); + $this->headerWidget->addStyleSheet('/css/globals/jquery/plugins/colorbox/colorbox-default.css'); + $this->headerWidget->addJavaScript('/js/globals/jquery-1.4.2.min.js'); + $this->headerWidget->addJavaScript('/js/globals/jquery-ui-1.8.5.custom.min.js'); + $this->headerWidget->addJavaScript('/js/globals/jquery.colorbox-min.js'); + $this->headerWidget->addJavaScript('/js/globals/core.js'); + $this->headerWidget->addJavaScript('/js/globals/admin.js'); + } + + protected function postExecute(\nano\core\routing\Routing $routing) { + $this->header = $this->headerWidget->getRenderedWidget(); + } +} \ No newline at end of file diff --git a/project/globals/widgets/exception/Exception.class.php b/project/globals/widgets/exception/Exception.class.php new file mode 100644 index 0000000..0bb0b79 --- /dev/null +++ b/project/globals/widgets/exception/Exception.class.php @@ -0,0 +1,34 @@ +exception = $exception; + } + + private function getExceptionData($exception){ + if($exception!==null){ + $data = array( + 'type' => get_class($exception), + 'message' => $exception->getMessage(), + 'trace_as_string' => $exception->getTraceAsString(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine() + ); + $this->exceptionData[] = $data; + $this->getExceptionData($exception->getPrevious()); + } + } + + public function execute(\nano\core\routing\Routing $routing,\nano\core\page\Page $pageInstance = null) { + + $this->exceptionData = array(); + $this->getExceptionData($this->exception); + + return 'project/globals/widgets/exception/views/exception.twig'; + } + +} \ No newline at end of file diff --git a/project/globals/widgets/exception/views/exception.twig b/project/globals/widgets/exception/views/exception.twig new file mode 100644 index 0000000..0ee68e9 --- /dev/null +++ b/project/globals/widgets/exception/views/exception.twig @@ -0,0 +1,14 @@ +
    +
    NanoPHP - Uncaught Exception
    + {% for data in exceptionData %} +
    +
    Message: {{ data.message }}
    +
    An '{{ data.type }}' exception was thrown in {{ data.file }} on line #{{ data.line }}
    +
    +
    +{{ data.trace_as_string }}
    +
    +
    +
    + {% endfor %} +
    \ No newline at end of file diff --git a/project/globals/widgets/loggingbar/LoggingBar.class.php b/project/globals/widgets/loggingbar/LoggingBar.class.php new file mode 100644 index 0000000..c24e9ed --- /dev/null +++ b/project/globals/widgets/loggingbar/LoggingBar.class.php @@ -0,0 +1,22 @@ +isAjaxRequest = $routing->isAjax(); + $this->memory = number_format((memory_get_peak_usage() / 1048576),2); + $this->time = number_format($log->getExecuteTime(),2); + $this->errors = $log->getErrors(); + $this->queries = $log->getQueries(); + $this->cachedInfos = $log->getCachedInfos(); + $this->environment = \nano\core\config\Config::getEnvironment(); + $this->config = \nano\core\config\Config::getConfig(); + + return 'project/globals/widgets/loggingbar/views/log.php'; + } + +} \ No newline at end of file diff --git a/project/globals/widgets/loggingbar/views/log.php b/project/globals/widgets/loggingbar/views/log.php new file mode 100644 index 0000000..00f5e79 --- /dev/null +++ b/project/globals/widgets/loggingbar/views/log.php @@ -0,0 +1,129 @@ + + $value){ + $key = ($parentKey=='')? $key : $parentKey.'.'.$key; + if(is_array($value)){ + recursiveArrayDisplay($value,$key,($depth+1)); + } else { + echo('
    '.$key.' : '.$value.'
    '); + } + } + } +} +if($isAjaxRequest){ + ?> +
    + Logging Bar | '.$memory.' MB | T: '.$time.'ms | Q: '.count($queries).''); ?> | Show + +
    + Logging Bar '.count($errors).' errors on this page)'; ?> | '.$memory.' MB | Page Load Time: '.$time.'ms | Logged Queries: '.count($queries).''); ?> | Show Details | Close + + +
    $_GET Information
    +
    + No information to display...
    '); + }else{ + recursiveArrayDisplay($_GET); + } + ?> +
    +
    $_POST Information
    +
    + No information to display...
    '; + }else{ + recursiveArrayDisplay($_POST); + } + ?> +
    +
    $_FILES Information
    +
    + No information to display...
    '); + }else{ + recursiveArrayDisplay($_FILES); + } + ?> +
    +
    Logged Errors
    +
    + No information to display...
    '); + }else{ + foreach($errors as $error){ + if(isset($error['stackTrace'])){ + echo('
    Type ['.$error['type'].'] : Message ['.$error['message'].']
    '.$error['stackTrace'].'
    '); + } else { + echo('
    Type ['.$error['type'].'] : Message ['.$error['message'].']
    '); + } + } + } + ?> + +
    Logged Queries
    +
    + No information to display...
    '); + }else{ + foreach($queries as $query){ + echo('
    '.$query.'
    '); + } + } + ?> + +
    Cached Info
    +
    + No information to display...
    '); + }else{ + foreach($cachedInfos as $cachedInfo){ + echo('
    '.$cachedInfo.'
    '); + } + } + ?> + +
    Environment
    +
    +
    +
    + + + \ No newline at end of file diff --git a/project/lib/swift/Swift.class.php b/project/lib/swift/Swift.class.php new file mode 100644 index 0000000..de75adb --- /dev/null +++ b/project/lib/swift/Swift.class.php @@ -0,0 +1,21 @@ + \nano\core\config\Config::get('twig_debug'), + 'cache' => $cachePath, + 'auto_reload' => \nano\core\config\Config::get('twig_auto_reload'), + 'trim_blocks' => true, + 'autoescape' => false + )); + self::$twig->addExtension(new \project\lib\twig\filters\CoreFilters()); + } + + public static function getInstance() { + return self::$twig; + } +} \ No newline at end of file diff --git a/project/lib/twig/filters/CoreFilters.class.php b/project/lib/twig/filters/CoreFilters.class.php new file mode 100644 index 0000000..871f53c --- /dev/null +++ b/project/lib/twig/filters/CoreFilters.class.php @@ -0,0 +1,39 @@ + new \Twig_Filter_Function('i18n'), + 'rawurlencode' => new \Twig_Filter_Function('rawurlencode'), + 'strcap' => new \Twig_Filter_Function(__CLASS__.'::strcap'), + 'inc' => new \Twig_Filter_Function(__CLASS__.'::inc'), + 'htmlencode' => new \Twig_Filter_Function('htmlentities'), + 'htmldecode' => new \Twig_Filter_Function('html_entity_decode'), + 'striptags' => new \Twig_Filter_Function('strip_tags'), + 'dump' => new \Twig_Filter_Function('var_dump'), + 'inarray' => new \Twig_Filter_Function('in_array'), + ); + } + + public function getName() { + return 'project'; + } + + public static function strcap($str,$len) { + return (strlen($str)>$len)? substr($str,0,$len).'...' : $str; + } + + public static function inc($str) { + switch($str){ + case 'log': + if(\project\config\Config::get('logging_bar')==true){ + $loggingBarWidget = new \project\globals\widgets\loggingbar\LoggingBar(); + return $loggingBarWidget->getRenderedWidget(); + } + break; + } + return ''; + } +} \ No newline at end of file diff --git a/project/session/Session.class.php b/project/session/Session.class.php new file mode 100644 index 0000000..314f17d --- /dev/null +++ b/project/session/Session.class.php @@ -0,0 +1,84 @@ +getTable('User','nanophp')->retrieveByEmailAndPassword($email,self::getPasswordHash($password)); + if($user==null){ + return false; + } else { + $session = \nano\core\db\ORM::getInstance()->getTable('Session','nanophp')->retrieveByUser($user->getId()); + if($session==null){ + //no session has been found, so make one + try{ + $session = new \project\db\om\nanophp\Session(); + $session->setUser($user->getId()); + $session->setToken(self::generateNewToken()); + $session->setIp($_SERVER['REMOTE_ADDR']); + $session->setCreated('MYSQL_NOW()'); + $session->save(); + } catch (\Exception $e) { + //do nothing - just log + } + } + self::setAttribute('user-id',$user->getId(),$expires); + self::setAttribute('user-token',$session->getToken(),$expires); + self::$user = $user; + return true; + } + } + + public static function logout($redirect=true) { + self::$user = null; + self::setAttribute('user-id','',-3600); + self::setAttribute('user-token','',-3600); + if($redirect){ + \nano\core\routing\Routing::getInstance()->getResponse()->pageRedirect('/login'); + } + } + + public static function authenticate() { + if(self::isSession()) { + return true; + } else { + //attempt to authenticate + try{ + $session = \nano\core\db\ORM::getInstance()->getTable('Session','nanophp')->retrieveByUserAndToken(self::getAttribute('user-id'),self::getAttribute('user-token')); + if($session==null){ + return false; + } else { + self::$user = $session->getUser(); + self::$isSession = true; + return true; + } + } catch (\Exception $e) { + + } + } + return false; + } + + public static function isAdmin() { + if(self::authenticate()) { + if(self::$user->getIsAdmin()=='1') { + return true; + } + } + return false; + } + + public static function isSuperAdmin() { + if(self::authenticate()) { + if(self::$user->getIsSuperAdmin()=='1') { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/sql/nanophp_structure.sql b/sql/nanophp_structure.sql new file mode 100644 index 0000000..30565fa --- /dev/null +++ b/sql/nanophp_structure.sql @@ -0,0 +1,94 @@ +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + +-- +-- Database: `nanophp` +-- +CREATE DATABASE IF NOT EXISTS `nanophp` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci; +USE `nanophp`; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Auths` +-- + +CREATE TABLE IF NOT EXISTS `Auths` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `uid` int(11) NOT NULL COMMENT 'User ID (our side)', + `fuid` char(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Foreign User ID (their side)', + `perams` char(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'JSON Encoded Perams (any extra perams)', + PRIMARY KEY (`id`), + KEY `fuid` (`fuid`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='This is to be used with assoc. a user with External Accounts' AUTO_INCREMENT=1 ; + +-- +-- Dumping data for table `Auths` +-- + + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Languages` +-- + +CREATE TABLE IF NOT EXISTS `Languages` ( + `code` char(7) COLLATE utf8_unicode_ci NOT NULL, + `key` char(255) COLLATE utf8_unicode_ci NOT NULL, + `value` text COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`code`,`key`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Dumping data for table `Languages` +-- + + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Sessions` +-- + +CREATE TABLE IF NOT EXISTS `Sessions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `User` int(11) NOT NULL, + `token` char(255) COLLATE utf8_unicode_ci NOT NULL, + `ip` int(11) NOT NULL, + `created` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `token` (`token`,`ip`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ; + +-- +-- Dumping data for table `Sessions` +-- + + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Users` +-- + +CREATE TABLE IF NOT EXISTS `Users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `language` char(7) COLLATE utf8_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `is_active` tinyint(1) NOT NULL DEFAULT '0', + `is_admin` tinyint(1) NOT NULL DEFAULT '0', + `is_super_admin` tinyint(1) NOT NULL DEFAULT '0', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `email` (`email`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=2 ; + +-- +-- Dumping data for table `Users` +-- + +INSERT INTO `Users` (`id`, `language`, `email`, `password`, `is_active`, `is_admin`, `is_super_admin`, `created_at`, `updated_at`) VALUES +(1, 'en_GB', 'chris@nanophp.org', 'testing', 1, 1, 1, '2010-12-07 11:57:43', '2010-12-07 11:57:43'); diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 0000000..557aa2c --- /dev/null +++ b/web/.htaccess @@ -0,0 +1,20 @@ +Options +FollowSymLinks +Options +Indexes + + Header set PHPFramework "NanoPHP Version 2" + + +RewriteEngine On + +RewriteRule ^favicon.ico$ favicon.ico [L] +RewriteRule ^(css|js|img|assets)/(.*)$ $1/$2 [L] + +# ####### Dev ########## +RewriteCond %{HTTP_HOST} ^www\.example\.dev$ +RewriteRule .* index.php [L,E=PROJECT_APP:front_end,E=PROJECT_ENV:Dev] +# ####################### + +# ####### Dev ########## +RewriteCond %{HTTP_HOST} ^admin\.example\.dev$ +RewriteRule .* index.php [L,E=PROJECT_APP:back_end,E=PROJECT_ENV:Dev] +# ####################### \ No newline at end of file diff --git a/web/css/globals/admin.css b/web/css/globals/admin.css new file mode 100644 index 0000000..951627d --- /dev/null +++ b/web/css/globals/admin.css @@ -0,0 +1,44 @@ +body { + font-family: Verdana, Arial; + font-size: 12px; +} + +.connectedSortableContainer { + min-height:300px; + max-height:600px; + overflow: auto; + background:#FFFFFF; + margin-top:0px; + padding-top:0px; +} +.connectedSortable { + list-style-type:none; + padding: 5px; +} +.connectedSortable li { + padding: 10px; + margin-left: 0px; + margin-bottom:5px; + margin-right:5px; + cursor: pointer !important; +} +.list-placeholder { + height: 70px; +} +.row-partition { + margin-top: 10px; + padding: 10px 5px 10px 5px; +} + +.nano-admin-list-tr { } +.nano-admin-list-tr.even { } +.nano-admin-list-tr.odd { background: url('/img/globals/tiles/90percentwhite.png'); } + +.nano-admin-state-highlight { padding: 0.5em; margin-bottom: 10px; margin-top: 10px; } +.nano-admin-state-error { padding: 0.5em; margin-bottom: 10px; margin-top: 10px; } + +.nano-admin-widget-left-menu { padding: 0.5em; width:200px; margin-bottom: 10px; } +.nano-admin-widget-left-menu h3 { margin: 0; font-size: 1em; padding: 6px; margin-bottom: 10px; } + +.nano-admin-widget { padding: 0.5em; margin-bottom: 10px; } +.nano-admin-widget h3 { margin: 0; font-size: 1em; padding: 6px; margin-bottom: 10px; } \ No newline at end of file diff --git a/web/css/globals/default.css b/web/css/globals/default.css new file mode 100644 index 0000000..ed8226c --- /dev/null +++ b/web/css/globals/default.css @@ -0,0 +1,94 @@ +.form-error { + margin-bottom: 10px; + padding-top: 10px; + padding-bottom: 10px; + background: #F2D8DB; + border: 1px solid #913C33; + text-align: center; +} + +.form-message { + margin-bottom: 10px; + padding-top: 10px; + padding-bottom: 10px; + background: #E5F2D8; + border: 1px solid #679133; + text-align: center; +} + +.form-text-container { + padding: 5px; + border: 1px #1C1C1C solid; + background: #FFFFFF; +} + +.form-text { + font-size: 1.1em; + background: none; + border: none; +} + +.form-file { + font-size: 1.1em; +} + +textarea.form-textarea { + width: 800px; + height: 200px; + border: 1px solid #1C1C1C; + padding: 5px; + font-family: Verdana, Arial; + font-size: 0.9em; +} + +.form-password { + font-size: 1.1em; + background: none; + border: none; +} + +.form-submit-button { + font-size: 1.1em; +} + +.widget-title { + padding-top: 10px; + padding-bottom: 2px; + margin-bottom: 10px; + font-size: 1.2em; + border-bottom: 1px solid #1C1C1C; + text-align: left; +} + +.core-logging-bar { + position: absolute; + z-index: 1000; + top: 0; + right: 0; + padding: 10px; + background: url('/img/globals/tiles/90percentwhite.png'); + font-size: 0.8em; + border-bottom: 1px #D7D7D7 solid; + border-left: 1px #D7D7D7 solid; +} + +.core-logging-bar-title { + margin: 6px 0px 2px 0px; + padding-bottom: 2px; + font-weight: bold; + border-bottom: 1px #D7D7D7 solid; +} + +.core-logging-bar-body { + margin: 2px 0px 2px 0px; + font-weight: normal; +} + +.core-logging-bar-item { + margin: 1px 0px 1px 0px; + font-weight: normal; +} + +.click-span { + cursor: pointer !important; +} \ No newline at end of file diff --git a/web/css/globals/jquery/plugins/colorbox/colorbox-default.css b/web/css/globals/jquery/plugins/colorbox/colorbox-default.css new file mode 100755 index 0000000..849afb2 --- /dev/null +++ b/web/css/globals/jquery/plugins/colorbox/colorbox-default.css @@ -0,0 +1,36 @@ +/* + ColorBox Core Style + The following rules are the styles that are consistant between themes. + Avoid changing this area to maintain compatability with future versions of ColorBox. +*/ +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} +#cboxOverlay{position:fixed; width:100%; height:100%;} +#cboxMiddleLeft, #cboxBottomLeft{clear:left;} +#cboxContent{position:relative;} +#cboxLoadedContent{overflow:auto;} +#cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;} +#cboxTitle{margin:0;} +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;} +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} + +/* + ColorBox example user style + The following rules are ordered and tabbed in a way that represents the + order/nesting of the generated HTML, so that the structure easier to understand. +*/ +#cboxOverlay{background:#000;} + +#colorbox{} + #cboxContent{margin-top:20px;} + #cboxLoadedContent{background:#000; padding:5px;} + #cboxTitle{position:absolute; top:-20px; left:0; color:#ccc;} + #cboxCurrent{position:absolute; top:-20px; right:0px; color:#ccc;} + #cboxSlideshow{position:absolute; top:-20px; right:90px; color:#fff;} + #cboxPrevious{position:absolute; top:50%; left:5px; margin-top:-32px; background:url(/img/globals/colorbox/style3/controls.png) top left no-repeat; width:28px; height:65px; text-indent:-9999px;} + #cboxPrevious.hover{background-position:bottom left;} + #cboxNext{position:absolute; top:50%; right:5px; margin-top:-32px; background:url(/img/globals/colorbox/style3/controls.png) top right no-repeat; width:28px; height:65px; text-indent:-9999px;} + #cboxNext.hover{background-position:bottom right;} + #cboxLoadingOverlay{background:#000;} + #cboxLoadingGraphic{background:url(/img/globals/colorbox/style3/loading.gif) center center no-repeat;} + #cboxClose{position:absolute; top:5px; right:5px; display:block; background:url(/img/globals/colorbox/style3/controls.png) top center no-repeat; width:38px; height:19px; text-indent:-9999px;} + #cboxClose.hover{background-position:bottom center;} \ No newline at end of file diff --git a/web/css/globals/jquery/plugins/colorbox/colorbox1.css b/web/css/globals/jquery/plugins/colorbox/colorbox1.css new file mode 100755 index 0000000..5aed2b7 --- /dev/null +++ b/web/css/globals/jquery/plugins/colorbox/colorbox1.css @@ -0,0 +1,62 @@ +/* + ColorBox Core Style + The following rules are the styles that are consistant between themes. + Avoid changing this area to maintain compatability with future versions of ColorBox. +*/ +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} +#cboxOverlay{position:fixed; width:100%; height:100%;} +#cboxMiddleLeft, #cboxBottomLeft{clear:left;} +#cboxContent{position:relative; overflow:hidden;} +#cboxLoadedContent{overflow:auto;} +#cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;} +#cboxTitle{margin:0;} +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;} +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} + +/* + Example user style + The following rules are ordered and tabbed in a way that represents the + order/nesting of the generated HTML, so that the structure easier to understand. +*/ +#cboxOverlay{background:url(/img/globals/colorbox/style1/overlay.png) 0 0 repeat;} +#colorbox{} + #cboxTopLeft{width:21px; height:21px; background:url(/img/globals/colorbox/style1/controls.png) -100px 0 no-repeat;} + #cboxTopRight{width:21px; height:21px; background:url(/img/globals/colorbox/style1/controls.png) -129px 0 no-repeat;} + #cboxBottomLeft{width:21px; height:21px; background:url(/img/globals/colorbox/style1/controls.png) -100px -29px no-repeat;} + #cboxBottomRight{width:21px; height:21px; background:url(/img/globals/colorbox/style1/controls.png) -129px -29px no-repeat;} + #cboxMiddleLeft{width:21px; background:url(/img/globals/colorbox/style1/controls.png) left top repeat-y;} + #cboxMiddleRight{width:21px; background:url(/img/globals/colorbox/style1/controls.png) right top repeat-y;} + #cboxTopCenter{height:21px; background:url(/img/globals/colorbox/style1/border.png) 0 0 repeat-x;} + #cboxBottomCenter{height:21px; background:url(/img/globals/colorbox/style1/border.png) 0 -29px repeat-x;} + #cboxContent{background:#fff;} + #cboxLoadedContent{margin-bottom:28px;} + #cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;} + #cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;} + #cboxSlideshow{position:absolute; bottom:4px; right:30px; color:#0092ef;} + #cboxPrevious{position:absolute; bottom:0; left:0px; background:url(/img/globals/colorbox/style1/controls.png) -75px 0px no-repeat; width:25px; height:25px; text-indent:-9999px;} + #cboxPrevious.hover{background-position:-75px -25px;} + #cboxNext{position:absolute; bottom:0; left:27px; background:url(/img/globals/colorbox/style1/controls.png) -50px 0px no-repeat; width:25px; height:25px; text-indent:-9999px;} + #cboxNext.hover{background-position:-50px -25px;} + #cboxLoadingOverlay{background:url(/img/globals/colorbox/style1/loading_background.png) center center no-repeat;} + #cboxLoadingGraphic{background:url(/img/globals/colorbox/style1/loading.gif) center center no-repeat;} + #cboxClose{position:absolute; bottom:0; right:0; background:url(/img/globals/colorbox/style1/controls.png) -25px 0px no-repeat; width:25px; height:25px; text-indent:-9999px;} + #cboxClose.hover{background-position:-25px -25px;} + +/* + The following fixes png-transparency for IE6. + It is also necessary for png-transparency in IE7 & IE8 to avoid 'black halos' with the fade transition + + Since this method does not support CSS background-positioning, it is incompatible with CSS sprites. + Colorbox preloads navigation hover classes to account for this. + + !! Important Note: AlphaImageLoader src paths are relative to the HTML document, + while regular CSS background images are relative to the CSS document. +*/ +.cboxIE #cboxTopLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderTopLeft.png, sizingMethod='scale');} +.cboxIE #cboxTopCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderTopCenter.png, sizingMethod='scale');} +.cboxIE #cboxTopRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderTopRight.png, sizingMethod='scale');} +.cboxIE #cboxBottomLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderBottomLeft.png, sizingMethod='scale');} +.cboxIE #cboxBottomCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderBottomCenter.png, sizingMethod='scale');} +.cboxIE #cboxBottomRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderBottomRight.png, sizingMethod='scale');} +.cboxIE #cboxMiddleLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderMiddleLeft.png, sizingMethod='scale');} +.cboxIE #cboxMiddleRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style1/internet_explorer/borderMiddleRight.png, sizingMethod='scale');} diff --git a/web/css/globals/jquery/plugins/colorbox/colorbox2.css b/web/css/globals/jquery/plugins/colorbox/colorbox2.css new file mode 100755 index 0000000..bd86df8 --- /dev/null +++ b/web/css/globals/jquery/plugins/colorbox/colorbox2.css @@ -0,0 +1,41 @@ +/* + ColorBox Core Style + The following rules are the styles that are consistant between themes. + Avoid changing this area to maintain compatability with future versions of ColorBox. +*/ +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} +#cboxOverlay{position:fixed; width:100%; height:100%;} +#cboxMiddleLeft, #cboxBottomLeft{clear:left;} +#cboxContent{position:relative; overflow:visible;} +#cboxLoadedContent{overflow:auto;} +#cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;} +#cboxTitle{margin:0;} +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;} +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} + +/* + ColorBox example user style + The following rules are ordered and tabbed in a way that represents the + order/nesting of the generated HTML, so that the structure easier to understand. +*/ +#cboxOverlay{background:#fff;} + +#colorbox{} + #cboxContent{margin-top:32px;} + #cboxLoadedContent{background:#000; padding:1px;} + #cboxLoadingGraphic{background:url(/img/globals/colorbox/style2/loading.gif) center center no-repeat;} + #cboxLoadingOverlay{background:#000;} + #cboxTitle{position:absolute; top:-22px; left:0; color:#000;} + #cboxCurrent{position:absolute; top:-22px; right:205px; text-indent:-9999px;} + #cboxSlideshow, #cboxPrevious, #cboxNext, #cboxClose{text-indent:-9999px; width:20px; height:20px; position:absolute; top:-20px; background:url(/img/globals/colorbox/style2/controls.png) 0 0 no-repeat;} + #cboxPrevious{background-position:0px 0px; right:44px;} + #cboxPrevious.hover{background-position:0px -25px;} + #cboxNext{background-position:-25px 0px; right:22px;} + #cboxNext.hover{background-position:-25px -25px;} + #cboxClose{background-position:-50px 0px; right:0;} + #cboxClose.hover{background-position:-50px -25px;} + .cboxSlideshow_on #cboxPrevious, .cboxSlideshow_off #cboxPrevious{right:66px;} + .cboxSlideshow_on #cboxSlideshow{background-position:-75px -25px; right:44px;} + .cboxSlideshow_on #cboxSlideshow.hover{background-position:-100px -25px;} + .cboxSlideshow_off #cboxSlideshow{background-position:-100px 0px; right:44px;} + .cboxSlideshow_off #cboxSlideshow.hover{background-position:-75px -25px;} diff --git a/web/css/globals/jquery/plugins/colorbox/colorbox3.css b/web/css/globals/jquery/plugins/colorbox/colorbox3.css new file mode 100755 index 0000000..849afb2 --- /dev/null +++ b/web/css/globals/jquery/plugins/colorbox/colorbox3.css @@ -0,0 +1,36 @@ +/* + ColorBox Core Style + The following rules are the styles that are consistant between themes. + Avoid changing this area to maintain compatability with future versions of ColorBox. +*/ +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} +#cboxOverlay{position:fixed; width:100%; height:100%;} +#cboxMiddleLeft, #cboxBottomLeft{clear:left;} +#cboxContent{position:relative;} +#cboxLoadedContent{overflow:auto;} +#cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;} +#cboxTitle{margin:0;} +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;} +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} + +/* + ColorBox example user style + The following rules are ordered and tabbed in a way that represents the + order/nesting of the generated HTML, so that the structure easier to understand. +*/ +#cboxOverlay{background:#000;} + +#colorbox{} + #cboxContent{margin-top:20px;} + #cboxLoadedContent{background:#000; padding:5px;} + #cboxTitle{position:absolute; top:-20px; left:0; color:#ccc;} + #cboxCurrent{position:absolute; top:-20px; right:0px; color:#ccc;} + #cboxSlideshow{position:absolute; top:-20px; right:90px; color:#fff;} + #cboxPrevious{position:absolute; top:50%; left:5px; margin-top:-32px; background:url(/img/globals/colorbox/style3/controls.png) top left no-repeat; width:28px; height:65px; text-indent:-9999px;} + #cboxPrevious.hover{background-position:bottom left;} + #cboxNext{position:absolute; top:50%; right:5px; margin-top:-32px; background:url(/img/globals/colorbox/style3/controls.png) top right no-repeat; width:28px; height:65px; text-indent:-9999px;} + #cboxNext.hover{background-position:bottom right;} + #cboxLoadingOverlay{background:#000;} + #cboxLoadingGraphic{background:url(/img/globals/colorbox/style3/loading.gif) center center no-repeat;} + #cboxClose{position:absolute; top:5px; right:5px; display:block; background:url(/img/globals/colorbox/style3/controls.png) top center no-repeat; width:38px; height:19px; text-indent:-9999px;} + #cboxClose.hover{background-position:bottom center;} \ No newline at end of file diff --git a/web/css/globals/jquery/plugins/colorbox/colorbox4.css b/web/css/globals/jquery/plugins/colorbox/colorbox4.css new file mode 100755 index 0000000..c7e0a90 --- /dev/null +++ b/web/css/globals/jquery/plugins/colorbox/colorbox4.css @@ -0,0 +1,59 @@ +/* + ColorBox Core Style + The following rules are the styles that are consistant between themes. + Avoid changing this area to maintain compatability with future versions of ColorBox. +*/ +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} +#cboxOverlay{position:fixed; width:100%; height:100%;} +#cboxMiddleLeft, #cboxBottomLeft{clear:left;} +#cboxContent{position:relative; overflow:hidden;} +#cboxLoadedContent{overflow:auto;} +#cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;} +#cboxTitle{margin:0;} +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;} +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} + +/* + ColorBox example user style + The following rules are ordered and tabbed in a way that represents the + order/nesting of the generated HTML, so that the structure easier to understand. +*/ +#cboxOverlay{background:#fff;} + +#colorBox{} + #cboxTopLeft{width:25px; height:25px; background:url(/img/globals/colorbox/style4/border1.png) 0 0 no-repeat;} + #cboxTopCenter{height:25px; background:url(/img/globals/colorbox/style4/border1.png) 0 -50px repeat-x;} + #cboxTopRight{width:25px; height:25px; background:url(/img/globals/colorbox/style4/border1.png) -25px 0 no-repeat;} + #cboxBottomLeft{width:25px; height:25px; background:url(/img/globals/colorbox/style4/border1.png) 0 -25px no-repeat;} + #cboxBottomCenter{height:25px; background:url(/img/globals/colorbox/style4/border1.png) 0 -75px repeat-x;} + #cboxBottomRight{width:25px; height:25px; background:url(/img/globals/colorbox/style4/border1.png) -25px -25px no-repeat;} + #cboxMiddleLeft{width:25px; background:url(/img/globals/colorbox/style4/border2.png) 0 0 repeat-y;} + #cboxMiddleRight{width:25px; background:url(/img/globals/colorbox/style4/border2.png) -25px 0 repeat-y;} + #cboxContent{background:#fff;} + #cboxLoadedContent{margin-bottom:20px;} + #cboxTitle{position:absolute; bottom:0px; left:0; text-align:center; width:100%; color:#999;} + #cboxCurrent{position:absolute; bottom:0px; left:100px; color:#999;} + #cboxSlideshow{position:absolute; bottom:0px; right:42px; color:#444;} + #cboxPrevious{position:absolute; bottom:0px; left:0; color:#444;} + #cboxNext{position:absolute; bottom:0px; left:63px; color:#444;} + #cboxLoadingOverlay{background:url(/img/globals/colorbox/style4/loading.gif) 5px 5px no-repeat #fff;} + #cboxClose{position:absolute; bottom:0; right:0; display:block; color:#444;} + +/* + The following fixes png-transparency for IE6. + It is also necessary for png-transparency in IE7 & IE8 to avoid 'black halos' with the fade transition + + Since this method does not support CSS background-positioning, it is incompatible with CSS sprites. + Colorbox preloads navigation hover classes to account for this. + + !! Important Note: AlphaImageLoader src paths are relative to the HTML document, + while regular CSS background images are relative to the CSS document. +*/ +.cboxIE #cboxTopLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderTopLeft.png, sizingMethod='scale');} +.cboxIE #cboxTopCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderTopCenter.png, sizingMethod='scale');} +.cboxIE #cboxTopRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderTopRight.png, sizingMethod='scale');} +.cboxIE #cboxBottomLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderBottomLeft.png, sizingMethod='scale');} +.cboxIE #cboxBottomCenter{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderBottomCenter.png, sizingMethod='scale');} +.cboxIE #cboxBottomRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderBottomRight.png, sizingMethod='scale');} +.cboxIE #cboxMiddleLeft{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderMiddleLeft.png, sizingMethod='scale');} +.cboxIE #cboxMiddleRight{background:transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=/img/globals/colorbox/style4/internet_explorer/borderMiddleRight.png, sizingMethod='scale');} diff --git a/web/css/globals/jquery/plugins/colorbox/colorbox5.css b/web/css/globals/jquery/plugins/colorbox/colorbox5.css new file mode 100755 index 0000000..cf6a504 --- /dev/null +++ b/web/css/globals/jquery/plugins/colorbox/colorbox5.css @@ -0,0 +1,50 @@ +/* + ColorBox Core Style + The following rules are the styles that are consistant between themes. + Avoid changing this area to maintain compatability with future versions of ColorBox. +*/ +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} +#cboxOverlay{position:fixed; width:100%; height:100%;} +#cboxMiddleLeft, #cboxBottomLeft{clear:left;} +#cboxContent{position:relative; overflow:visible;} +#cboxLoadedContent{overflow:auto;} +#cboxLoadedContent iframe{display:block; width:100%; height:100%; border:0;} +#cboxTitle{margin:0;} +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;} +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} + +/* + ColorBox example user style + The following rules are ordered and tabbed in a way that represents the + order/nesting of the generated HTML, so that the structure easier to understand. +*/ +#cboxOverlay{background:#000;} + +#colorbox{} + #cboxTopLeft{width:14px; height:14px; background:url(/img/globals/colorbox/style5/controls.png) 0 0 no-repeat;} + #cboxTopCenter{height:14px; background:url(/img/globals/colorbox/style5/border.png) top left repeat-x;} + #cboxTopRight{width:14px; height:14px; background:url(/img/globals/colorbox/style5/controls.png) -36px 0 no-repeat;} + #cboxBottomLeft{width:14px; height:43px; background:url(/img/globals/colorbox/style5/controls.png) 0 -32px no-repeat;} + #cboxBottomCenter{height:43px; background:url(/img/globals/colorbox/style5/border.png) bottom left repeat-x;} + #cboxBottomRight{width:14px; height:43px; background:url(/img/globals/colorbox/style5/controls.png) -36px -32px no-repeat;} + #cboxMiddleLeft{width:14px; background:url(/img/globals/colorbox/style5/controls.png) -175px 0 repeat-y;} + #cboxMiddleRight{width:14px; background:url(/img/globals/colorbox/style5/controls.png) -211px 0 repeat-y;} + #cboxContent{background:#fff;} + #cboxLoadedContent{margin-bottom:5px;} + #cboxLoadingOverlay{background:url(/img/globals/colorbox/style5/loading_background.png) center center no-repeat;} + #cboxLoadingGraphic{background:url(/img/globals/colorbox/style5/loading.gif) center center no-repeat;} + #cboxTitle{position:absolute; bottom:-25px; left:0; text-align:center; width:100%; font-weight:bold; color:#7C7C7C;} + #cboxCurrent{position:absolute; bottom:-25px; left:58px; font-weight:bold; color:#7C7C7C;} + + #cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{position:absolute; bottom:-29px; background:url(/img/globals/colorbox/style5/controls.png) 0px 0px no-repeat; width:23px; height:23px; text-indent:-9999px;} + #cboxPrevious{left:0px; background-position: -51px -25px;} + #cboxPrevious.hover{background-position:-51px 0px;} + #cboxNext{left:27px; background-position:-75px -25px;} + #cboxNext.hover{background-position:-75px 0px;} + #cboxClose{right:0; background-position:-100px -25px;} + #cboxClose.hover{background-position:-100px 0px;} + + .cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px; right:27px;} + .cboxSlideshow_on #cboxSlideshow.hover{background-position:-150px 0px;} + .cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px; right:27px;} + .cboxSlideshow_off #cboxSlideshow.hover{background-position:-125px 0px;} \ No newline at end of file diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_0_aaaaaa_40x100.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_0_eeeeee_40x100.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_0_eeeeee_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..e44f861be1ccdfec4275eb20d9b056f14992f649 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`Sv zZF_)9$%zYm;;c7~Kd+Iuj%U9o62cnl7#bi-T}u4XA(n|-?{<9$I)lN})z4*}Q$iB} D#5ytf literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_55_ffffff_40x100.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_55_ffffff_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_75_ffffff_40x100.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_glass_65_ffffff_1x400.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..42ccba269b6e91bef12ad0fa18be651b5ef0ee68 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouqzpV=978O6-=0?FV^9z|eBtf= z|7WztIJ;WT>{+tN>ySr~=F{k$>;_x^_y?afmf9pRKH0)6?eSP?3s5hEr>mdKI;Vst E0O;M1& literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png new file mode 100755 index 0000000000000000000000000000000000000000..5dcfaa9a0168d65db3b8de1401cdf4e795880fa9 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0l%l7LV~E7m3f3H_#V`FPu o==SIT|M?Z1Nk>0;^9VCB@IMn?A*z)j3{=eE>FVdQ&MBb@0KuRb)c^nh literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_25_0073ea_1x100.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_25_0073ea_1x100.png new file mode 100755 index 0000000000000000000000000000000000000000..7226bdbbbde2355c7950ce4e4a37fc8faa2c568b GIT binary patch literal 118 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0l&zqdATfWD>nNOgjEKclzo5vCsufFxY{7j;m>q|8@O*)PLY Q7HANIr>mdKI;Vst0JB*kMgRZ+ literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_50_dddddd_1x100.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-bg_highlight-soft_50_dddddd_1x100.png new file mode 100755 index 0000000000000000000000000000000000000000..b47a4da5243c82396c15beedbe1584489bcce5b8 GIT binary patch literal 92 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-icons_454545_256x240.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-icons_454545_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..59bd45b907c4fd965697774ce8c5fc6b2fd9c105 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/images/ui-icons_ffffff_256x240.png b/web/css/globals/jquery/plugins/ui/flick/images/ui-icons_ffffff_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..42f8f992c727ddaa617da224a522e463df690387 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/flick/jquery-ui-1.8.5.custom.css b/web/css/globals/jquery/plugins/ui/flick/jquery-ui-1.8.5.custom.css new file mode 100755 index 0000000..8cab205 --- /dev/null +++ b/web/css/globals/jquery/plugins/ui/flick/jquery-ui-1.8.5.custom.css @@ -0,0 +1,572 @@ +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Helvetica,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=2px&bgColorHeader=dddddd&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=50&borderColorHeader=dddddd&fcHeader=444444&iconColorHeader=0073ea&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=dddddd&fcContent=444444&iconColorContent=ff0084&bgColorDefault=f6f6f6&bgTextureDefault=03_highlight_soft.png&bgImgOpacityDefault=100&borderColorDefault=dddddd&fcDefault=0073ea&iconColorDefault=666666&bgColorHover=0073ea&bgTextureHover=03_highlight_soft.png&bgImgOpacityHover=25&borderColorHover=0073ea&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=dddddd&fcActive=ff0084&iconColorActive=454545&bgColorHighlight=ffffff&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=cccccc&fcHighlight=444444&iconColorHighlight=0073ea&bgColorError=ffffff&bgTextureError=01_flat.png&bgImgOpacityError=55&borderColorError=ff0084&fcError=222222&iconColorError=ff0084&bgColorOverlay=eeeeee&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=80&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=60&thicknessShadow=4px&offsetTopShadow=-4px&offsetLeftShadow=-4px&cornerRadiusShadow=0px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Helvetica, Arial, sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Helvetica, Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #dddddd; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #444444; } +.ui-widget-content a { color: #444444; } +.ui-widget-header { border: 1px solid #dddddd; background: #dddddd url(images/ui-bg_highlight-soft_50_dddddd_1x100.png) 50% 50% repeat-x; color: #444444; font-weight: bold; } +.ui-widget-header a { color: #444444; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #dddddd; background: #f6f6f6 url(images/ui-bg_highlight-soft_100_f6f6f6_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #0073ea; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #0073ea; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #0073ea; background: #0073ea url(images/ui-bg_highlight-soft_25_0073ea_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #ffffff; } +.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #dddddd; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #ff0084; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #ff0084; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #cccccc; background: #ffffff url(images/ui-bg_flat_55_ffffff_40x100.png) 50% 50% repeat-x; color: #444444; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #444444; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #ff0084; background: #ffffff url(images/ui-bg_flat_55_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #222222; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #222222; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_ff0084_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_ff0084_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_0073ea_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_666666_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_0073ea_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ff0084_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 2px; -webkit-border-top-left-radius: 2px; border-top-left-radius: 2px; } +.ui-corner-tr { -moz-border-radius-topright: 2px; -webkit-border-top-right-radius: 2px; border-top-right-radius: 2px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 2px; -webkit-border-bottom-left-radius: 2px; border-bottom-left-radius: 2px; } +.ui-corner-br { -moz-border-radius-bottomright: 2px; -webkit-border-bottom-right-radius: 2px; border-bottom-right-radius: 2px; } +.ui-corner-top { -moz-border-radius-topleft: 2px; -webkit-border-top-left-radius: 2px; border-top-left-radius: 2px; -moz-border-radius-topright: 2px; -webkit-border-top-right-radius: 2px; border-top-right-radius: 2px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 2px; -webkit-border-bottom-left-radius: 2px; border-bottom-left-radius: 2px; -moz-border-radius-bottomright: 2px; -webkit-border-bottom-right-radius: 2px; border-bottom-right-radius: 2px; } +.ui-corner-right { -moz-border-radius-topright: 2px; -webkit-border-top-right-radius: 2px; border-top-right-radius: 2px; -moz-border-radius-bottomright: 2px; -webkit-border-bottom-right-radius: 2px; border-bottom-right-radius: 2px; } +.ui-corner-left { -moz-border-radius-topleft: 2px; -webkit-border-top-left-radius: 2px; border-top-left-radius: 2px; -moz-border-radius-bottomleft: 2px; -webkit-border-bottom-left-radius: 2px; border-bottom-left-radius: 2px; } +.ui-corner-all { -moz-border-radius: 2px; -webkit-border-radius: 2px; border-radius: 2px; } + +/* Overlays */ +.ui-widget-overlay { background: #eeeeee url(images/ui-bg_flat_0_eeeeee_40x100.png) 50% 50% repeat-x; opacity: .80;filter:Alpha(Opacity=80); } +.ui-widget-shadow { margin: -4px 0 0 -4px; padding: 4px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .60;filter:Alpha(Opacity=60); -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; }/* + * jQuery UI Resizable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; }/* + * jQuery UI Autocomplete @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_aaaaaa_40x100.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_f4ff42_40x100.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_f4ff42_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..b107a980ddddbc9c08b02b04e33dcf3de880f45c GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!N$PA*qrS22FRK1>EalY(fIbdAyG2GY%hgoCU{CA|XMYc5@WbWd5Ba|)kFDpqCAg9VQ`v9pi{ERxU*!UHCWEJ|pUXO@ GgeCwJ0#3mI literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_ff3387_40x100.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_0_ff3387_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..d519995d13170d7cd8ee1da137025ea4f9bc83ea GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=6AkFlbYC z@iUuzU}CduZ<9^+-s(R$_Q+ajuHahLv}%DCbt$_G-pmapD-^Z>ox$Mg>gTe~DWM4f D_w7YM literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_80_ffffff_40x100.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_flat_80_ffffff_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_00bbeb_1x400.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_00bbeb_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..4e92d5fa5c5ebef2a8d5201b11308d1a721b496e GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAGVZ#9R3S1?oLr978O6-(IoiVsaE{dANUd zdWe|#$BheTHZDvzxF)>XU4VbnhK`SiD!S$h9(&@wy1VJwrz&+u5r(|lOO>)0SMu&W z>}_`av+<&Ab9e9Ut7eHmHSBqDfkRf>{depCL!pu@Dn4f&d|w;%y3OojfOu<}@xok# kEnIDjit6Xv2Y+Jy@K{EX>-*HbK&LQxy85}Sb4q9e064=)@c;k- literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_29d4ff_1x400.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_29d4ff_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..3def977b7c9e2a5851bf78f790083c10d7c1481a GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAGVZ#9R3S1YS_)WL$`5uP+{Z&y-hU^ ae05W|NMAhV#V`Tr0tQc4KbLh*2~7ZTTto~2 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_8cff0f_1x400.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-bg_glass_75_8cff0f_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..4e3b89d03a2fd81f6eeea5e06d3f363da2e6512c GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouqyjx%978O6-=5zn*kHipd@=uM zqYAT;n(b)=u4SCE%ms61hrd|j^ya!4lgFed+vk)iEMDN`sv7ph%UO}druA4QCgJ)dIlWTdhzvhTUFT;bLd)69DSy}{oLcSI;G3*dtp{4Qs zk5EAN+sbdsGj2v-jdENnB7a<>pJl(zf3E8M2^|gBqWUbKThF*DuJnl0_;>wuX&J`( WK{xYn?&1bo&*16m=d#Wzp$Py|>OKzu literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_000000_256x240.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_000000_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..7c211aa089a197902b68105e8bdc3834fe770203 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_1c1c1c_256x240.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_1c1c1c_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..2130f1add0fe3d78249ef8a0b8bb4c217cc741a7 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_ffffff_256x240.png b/web/css/globals/jquery/plugins/ui/nanophp/images/ui-icons_ffffff_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..42f8f992c727ddaa617da224a522e463df690387 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/nanophp/jquery-ui-1.8.5.custom.css b/web/css/globals/jquery/plugins/ui/nanophp/jquery-ui-1.8.5.custom.css new file mode 100755 index 0000000..00d59a4 --- /dev/null +++ b/web/css/globals/jquery/plugins/ui/nanophp/jquery-ui-1.8.5.custom.css @@ -0,0 +1,572 @@ +/* + * jQuery UI CSS Framework 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=0px&bgColorHeader=1c1c1c&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=1c1c1c&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=80&borderColorContent=b5b5b5&fcContent=1c1c1c&iconColorContent=1c1c1c&bgColorDefault=29d4ff&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=009fc7&fcDefault=000000&iconColorDefault=000000&bgColorHover=00bbeb&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=009fc7&fcHover=1c1c1c&iconColorHover=1c1c1c&bgColorActive=8cff0f&bgTextureActive=02_glass.png&bgImgOpacityActive=75&borderColorActive=75e000&fcActive=1c1c1c&iconColorActive=1c1c1c&bgColorHighlight=f4ff42&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=0&borderColorHighlight=ddf000&fcHighlight=1c1c1c&iconColorHighlight=1c1c1c&bgColorError=ff3387&bgTextureError=01_flat.png&bgImgOpacityError=0&borderColorError=e6005e&fcError=ffffff&iconColorError=ffffff&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #b5b5b5; background: #ffffff url(images/ui-bg_flat_80_ffffff_40x100.png) 50% 50% repeat-x; color: #1c1c1c; } +.ui-widget-content a { color: #1c1c1c; } +.ui-widget-header { border: 1px solid #1c1c1c; background: #1c1c1c url(images/ui-bg_highlight-soft_75_1c1c1c_1x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #009fc7; background: #29d4ff url(images/ui-bg_glass_75_29d4ff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #000000; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #000000; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #009fc7; background: #00bbeb url(images/ui-bg_glass_75_00bbeb_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #1c1c1c; } +.ui-state-hover a, .ui-state-hover a:hover { color: #1c1c1c; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #75e000; background: #8cff0f url(images/ui-bg_glass_75_8cff0f_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #1c1c1c; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #1c1c1c; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #ddf000; background: #f4ff42 url(images/ui-bg_flat_0_f4ff42_40x100.png) 50% 50% repeat-x; color: #1c1c1c; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #1c1c1c; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #e6005e; background: #ff3387 url(images/ui-bg_flat_0_ff3387_40x100.png) 50% 50% repeat-x; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_1c1c1c_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_1c1c1c_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_000000_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_1c1c1c_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_1c1c1c_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_1c1c1c_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 0px; -webkit-border-top-left-radius: 0px; border-top-left-radius: 0px; } +.ui-corner-tr { -moz-border-radius-topright: 0px; -webkit-border-top-right-radius: 0px; border-top-right-radius: 0px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; } +.ui-corner-br { -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } +.ui-corner-top { -moz-border-radius-topleft: 0px; -webkit-border-top-left-radius: 0px; border-top-left-radius: 0px; -moz-border-radius-topright: 0px; -webkit-border-top-right-radius: 0px; border-top-right-radius: 0px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } +.ui-corner-right { -moz-border-radius-topright: 0px; -webkit-border-top-right-radius: 0px; border-top-right-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } +.ui-corner-left { -moz-border-radius-topleft: 0px; -webkit-border-top-left-radius: 0px; border-top-left-radius: 0px; -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; } +.ui-corner-all { -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* + * jQuery UI Resizable 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; }/* + * jQuery UI Autocomplete 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar 1.8.6 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png new file mode 100755 index 0000000000000000000000000000000000000000..1bb13a2539636d70117fa6cb75f2d9640f544b83 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4C3?CzhDc0JKJ$6{&+`X>Kwab5 zhnIh~*(4mEedg>kG!R_;;_~ls9v=g?`3ods6n81H0fCwXbFAU6l{!2g43UOyR}J{| z7`98zK74U*<0H3{`O4bIq-pk)l6u6{1-oD!Mf4d3(AzhDc29op6x%fB^@yf9%QM z{Re#BDR+hhPC9UU0W&iLo6bq^=QXdyQl_0K2;A-SD=K|QRt1B~#_kS@M@irBe=PC& gIq&W2D1jfWR=PQ7Y?df{1C3?yboFyt=akR{0BZLyAOHXW literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_10_eceadf_60x60.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_10_eceadf_60x60.png new file mode 100755 index 0000000000000000000000000000000000000000..46567420785a91503f30de00eec3a331cd4a969e GIT binary patch literal 4429 zcmV-T5wh-yP)adQ zYt3&oCTgu1W1uxZyoej2_lD}{L9GRIE$n?Df;8Sa2dy=nbMPGly)~E_%nYTJICR6_ z2c;;!W8l+!UURLDV{UvJd+&qZ8@^-UI|f>HBM1bw);uGhVebtC&`L!GfYvI^09A=_XfZq0<_kk>KCNv?|b~EqUjO7=bQsG!`>$!gb@NDAAawRy?1)nqd`!LqDsM> z6Qz_e;s#)OJu}Pe`L*5iG~R3U3IKCXAi~dJ8K!RKioH*GA2?=c@z4g#2jXYWF|4&9 zDk!S?(5=->m-irRZ2-voonz2Q4Ku^qJ3R;(R26eAv{Ep}$aC{+zSeT%eaAr20!DHb z5tLG}cicEaZqAk7+Gj)HmU|z97G7&-Zi(Db+_oViP}Luge|nDt*v|p?*_mF`3@}4$ z{xCi6HO7RBV$O*k(cqEhzn^QtU?|1YP6HR*DGSxit;WNwwP6u1^a#NRa4_0q=b>Ax zP<<@BH)<&m*%^W&3K3wfmG5hNu#dDb9=a%*VYblv$%*FXUh#kM4=aq!f1n0Q7UNE%q@LCJ! z7*3q!I|jxW*!v_9!rYidWrP#(0 zD?|52(Qi7OW6-r0=5&vYF;Po_84{?>4Cm~yo+N^fF|pQ)2L66MuNheUb`zGI*=;YkE)MIN?g5s!oqv}KsHoYq?Mcjx$lOVyvyComdTg2+W) zFDd>r4^Ia?i%7U+gRk!xKjqV%(J|-DC}V08{8SZ6z}b5zoisBXJAtZ#V-~d<5r%pZ zJo1;GbAZ}TEyWM!z~Kgw@Gjt%Kj)y<>e+upFf)YEBbu&yxB7&D#1RQmLeR%?bc z?;RDzL$*gqi3mi+L(CuFOOzSL7@6z5LH?g%WT>^`(>uz8=3IhtgM>HT z%L*-;UR2ejp9D$2MoDMAPEi%CwJ{^D=A5WbI6NF^!K`aQ@MtelMKw#ncMMdp&I($s ziP|I}W+Mn$Dvmcm2`Akq=Nt>_iNK$NijoG` zT1ygczqm!CP@_qRP!PddE1{mAAfcaYC2E&aP)ZG86Gzip^PzxW(4v7Iy*>Q$Uh{(7 z4b2vjHhy@ycXZqDZAjWm02CD{t<16m2rS+t>cn9M?X1=LumRBGA;Uc4i$sHYw)aYF zJDJ$&+4u1DJVTRZt%re2(I|a!coB$bJ}7gWO^yig`Sh#;DvDYP)ae=W(7m@L5+|rz zQO)`W2x_T8A0JanoXPl~)M$%qbVMf5mu`2(`vI@nT?A?iE$m7<=*qK>kk z-m|4-!e__=0c;nU-3Yh-A;^d-ONk`gacd$yRcrBw?`$TnQTZTz@0=@u&y^8MJ)6rvNgRBhROMaJHY`$Fc@*ki*lX zwbq}$`5ro&h`obF-7szsETg#h> zS{w9{We6ajPls8sskpOK*m{w;U)o9#ayoc{i|G4`gsI1cp2h3eQW8JnQPUxnpyXhL z6u-_nfH)#EY?yBsJtgZjRYm4pZx@AK;G5}P4FdDZJq~8Spyg3jLm=2c;=m_czVoCI z0yyOn8+H&NPsPd(XDxFtDZ}o->;S3(@B7IQOM=0hP)fPE%~F6=O|A-Q$)G+#_*-jO z8>xC;0uoLCUOyVvoC^>S>+cxRD3s&}ubkUjO|)0m;8JSl3iGc&*1k#u;^&g;L~Awc z9BC+t#tVLIHm)egYq!LNQuOCD3{w@&^N^vn$7nP@)tcAv{CX~zkwWp5cKRw?=G5;9b9ZOZk zdAWPv&OzPhpy(|%j+sTNr}2>nuj_e|L}W8f|=#m_I^j8QdrGzxp zaLx{iSDkqH!}tR+XIX3KTrTy5lu8RH!Ms>Bt^ny+)NkY=etyVYtfp4Y*$ zoZmW|VNDfyL|nBYYBz2J;G($ZY$(f20ACK+@cSBb!tl$ny#`-gp#fbn2_j-Ak$;St zniv2SEl`$=47p>;UMRL*@UoC@?8!ftG4G^Y^3_D-9itcjykzu<;K!qYm$mQLqesPc zuGShppZ+t|Oo1V|y9lyvb-DMz4y9es`xF@w-eQMB36+g`>bbRG$JnuylDZQcanF;T zpy5)BCJyjxBI({I^<}=^*%2e*6&buWZ8@`AYdwv@B^dFO=#fj@ySNhafB*THv8256 zC_{!J%cNgxrT089?H`RiOG7C&BjSfWJ+s~XSqN}!`bkhy_z!KH_oABd*7@GOYQQXw z^F9jz=vq6xYx%>Mz4(;hfr(NLjq}$V8uZdTB$r~TLop&s(VvHvbi15rBGE9J3HM<;P{ zhVKsUOJYGbB|v_TwYrp=WePxkhF)buVG&*LwHY~U?fly3NU<~W9Z!#4<9#mik^?0F zCTP*Zq-R$vGJHNgrVd_9cT5rOnJyCYPt11MiU9;P$w*;W^DDzhjCNSA?huc)m~ADs zYc>@evG(55v#jkbpMZCIG?KnW{bhIWia~iqzITR1D>5b6F4D3swtv=I4?&pupzmOv z@{9$?aW{t{aCr>kTkiG6PLoEfpQFJ5Iap8|6 zl-`>&n-Bv$gzL`(WjH?MA3#nR*`Keq!Z<$F@LZOh6H!a^&)!at<&>{lN;u2UpgrVR zT43#!g}SIOiMiOkQ`a4Zz?(qRg{W!>S1YH^0s+v+QBgH$Ml~V)Nl1R&al2R0{ zy1@ZS#pC7w9J({t0f~9Dr=N0UNhN|{^R|dY!CHOBM0u9->Dz6+>Sb%y8Mu*B-d6La zm>hnw#FbJWHbtGP1U3YK+R6T#_ZQ(9l?{!*k5N=gL47B0F0OfAHUdD*ibX28H8hgW?q$(gZ96xOdf3l zPE4G7mP{j8MFq9CME_5_D_2Ubhj~ z;93$JeHxndkndp~=LFN>5yvP4Vw)!(mWKL%$4@0NGt^R@c+~3Z04;x;@~bhkr}Yts zuM4S4qBgI?%GO$QaXi!s?rG4{wR#Ks>HQzO8=O7+%7Ue*0ngE%qurQV6gED0S?%-b zQM*S*OeqQ_+G+}T^8o?*0U|Dt*|DEPlBW{>*(~^t6m!j7F(NawTy?S-g2kkw_tdxE zWf>A35)OjeyCFcJ<(d;WkX)_tpVwoC`ltt-G>q^9TQI-z`9Wp&y9`4Pxi~EI_v{?O zCIQHK@D=HL@3E3JgT#!V2XZlfV?Dyc)zLLmkzmNzD?+L&D$CB5Lw=XI*=M>%%g^r| zeIvJy<-@WjJCG<^E9v!DGW8l6i>2EmqI4gvP-Eku?UiosdNFr4z1g6^_@>9F_ee>g zR&`D5);?b=Al*~-C9ikb#fbv*$3t`8&tzRd=MhB+x}u+y4QDheVJZ)myF$Gs?q&I@pSfcN2i z%6{uO4aDzXPv6;+{0IX3?8Jb*H@HXU??l%BF=lf7{gf;U4Q6+ry9ax$wfa(vir2~$ z3og!osXn-tMdhE~=K7(B%zU&hqnw|K3obJ=<4#vh|8vBn$qaeE7DR~=#^;%H`9OS(4K8B-Rr+{1&!8R6 z#Y$P{#F&LNln=enPGO(@>$Uiu{{Ton&E{avl@p1-hPrLW{bSDye8vZH;t@=qpoMR0=wo1)(RBNDC<{w%tshwr@l zA z>yorM4n&1*;QgO;zxyn>!26@-xDT3|SRy*Qy9#`fQmK^8^4Fi=V~i2K_t^J6?)#2X zO5FDy5fP=77-Ph;EOFm=EXxwhvP4A0ec$nVy<*$8IF2J)Yf)>Bh=}7jVp*2hwk^){ zj9Tmb&KF~hF`|?bt+hDMGxmLt^E_i+*NBKX&oh=~iFI9LS(Z4CBT6Z;u4}wruc)=o zdzk0CuDSMQS>AvD`2KA$d220}Wr^NzW(EC=oF?e_hwS2Sh|f)LQ3yYOT`<5i!RbWBP24 zy{_vt-gRBm0c}~92?aI|OnH#S01GxX#+V!889Z4^iCXL2^t!GS4J-sWg9yCJvy2Oj z9b)$IzV9=HxL&W<{Jhp0z4y04e*XAAYOQm@wm6IA=2A*5%Mz`%xiAJVrOf{gZ!gvx z>b*ZaQ%aeh^Tyx@yoq!CoxR!z7zWZ{ z?AF?hHRgT2UK3?D&VAq0Q~SP8QUZ0L0}q1?3<^RSyI?i0bzP&iHdzo0^1HP*;|$NS z5nz6v=k(rnT@%*Zwmry@dsy5UBaY*kKi73d?|p6zZ^7!sIT>c#wrOaLOnBh=hKFE2 z;MrQ676Rh;eNXzwGi)3PG8s&XQTQw~Jq!#?Bi=;Cn5+xW8Qw-k?|rVB%i%y;Exq^Y z5ppSHVTQ$eu;psHe?=uWaDY5T+j4|ftKoBDw#}VgwP7iazGr*s9 zbD!CMrX~J?bg~7vZJUeBL}6<(2Ad~5fE)iaK4>Dw*|f)D9zyB$dOgC2@p+ywbeJMi z=I{Uh^=DWKInROpPh^6(xR}V#8v0Hpz@BGV5Uid>4s+VOnVngW`NMFBfD0DG+C1#k z0ugz048X!!0FX4qESNhyp zj}mvLcNYu9U=}HCthF}N94P}s5(1DS|3i@Ww)J)%$5|qWB=b+SSp+aR1%s`KIx=aQ zO?&T;l;#w{!toS_a0Z}4B4(Kj!@JsIoGd3X3L8YWa&IZ+Ej)miOFx%{Jct)r05R=|RWm>LQKB*nYH7UlP-S_=b(Xa`~ zl0{lv5CY_63`)@Lg zJ1VRNlt91*iodRF#tsu@3At_CBq3K%w(NPHk5~u(IR~TR*{sVLKT6+}#Ij3jQli|3g>DdiFeTKCNYW(QlE3Ii+-cV6`A47erPRW063ty4t8G&9c&9Z zrC?3skIhX*lY~aZS5GYWxy;(+e0!K@ZSZU~pib(v;rYAw{w6IZp-W-*b^XZzq=9^~ zc~?$u4!C)M!H}dd_Hi6jQsVjCZ+4@ec+b@ys3Z14>g-x_gLO5(Z3I}F9+ew##Jfqt zb+fA)!``Ukng4f6K|Y>P{TorlsyiKwImUPk4?2x7FS#re2p5ct#VOHHLMAl28!Q6& z6EaR47%Yo^(7?DLf&6N*gyHc%X%aLQsv#!GT?MySi9g~LIqAkbmZb=H6Q2-)E6m(k zq__*h+Q=GiJ}_o(1h@%F?k=UgTRgx8>vA&*9a1riyP*z&z>2P?U8fqpFf9g$RQ?TB zT-FH8l`qC}n+Wt2Hbq;8oXHOZ+nT_M`w0t^13hT8HH8G^1V6Yp?Zq3;Ac2kxahWx_ z5#(CHS);hZVF5JbEh2J<0V^ZqEfBl_(w&I|&;Of;@Fe5p!o|dgXz__7%uoEn3ANT( zzJL4Ai1=!R=Z>PG&Tlg>hQM%iFo3TA2L(P6;QCa{;sEkH5XBL&Hi9Of9g}?srCRGd z5985@uMWjzHPwH)@u(Xy7}uzKeS!&$$>`%iN`uSpNL} zH5_NYupoKZ7G^LO?|5RN&Z|bor<9k8&4=TDL%>y$H;hqyDw%uf5fR0#%!gJl+qOMg zI>aIHA;yUjOF&9Gn3(&$S+{=4pH6MWp~=c2#Kw@6{tV)?+-+{6`;6Fd!n)S4$V(=$ zr>CkZ|2(`lftU>0q+7Z^ZM3KINs05h4FU5%koW87j{!8@C5{u5R zh)c%taV~~{!SlJ4kA9{DAS;ACLYvLbw_pBln-HV9@yGZ1Z6C-Dr10A%B%5`mg+4~h z<59mmVgZ6Td6vuFmVMu&Ai=Cq%$8ZZI);4Zcp>)4!CnM~2kZ%i9RfffBRQA*%IITm zziY;tuIq|I3bMyOP3BGo_q&To9J+hycrw)ajK%oy3+DDQigQ06C1W@Z;$;)8dtSBH z_ZtASXDjIl0RVXhVPXsb^brVtJCDg>Y!@mlG^Zr3DIv=|h#S1J9FOhnDpcV0a{eW?J-kXM%K@vtYks57o4gzJ?;S$$x4iyS|pZ)Nl z-?nk}fvRDVK0xy(eadHHfw(|V=Tr!UeFp6=2!_U4UNiGCu5)&4?cIn6nph^QC@e(n zn<2#Ic1?bjFyf2Y@FHyYe}1tAMMpP<`HeohD#S2_ir>=1X`ia3>6qN^%;|S2=!!uew%*lSHZ1rgXkOLM@-dH$$*3uFKxD#n>=S~KjHSW%Db zy2kSR*U#Zse3^(c4MH*z0WsIdOvY#AYr_*OM?7DI;a+zp(BHIuaE4D1AD&BwajopD zSkk#o%V$_M;;UtY75${Y~|*jlVch~VPvNc zt1YjI4f;d_&jNG5kOL363tMZALZ{Fo0jnFLR86j`uzV&l#0Z{+ZHYyoX2IjWTJCDl ncXnGA{OfOX&WZaj zIF21-PI%|gdj|l75cIRwI(+c(&H(^=?ICh-pg_IIv3;=-hyZ`{lvE#aK#264` z#LsKYiEY2eir|aPQT5Ea$5dbbZnd4ZlK$ilrUeX#`>u}Ct&WS9RPpHKhbdS+Fhm;~}ZR*ye58QV_&YQXfPb9{mE=;4;y4>A62ZVsUd)(KJau;lQ zLkK~w&+l;oyqXx*E_%+1eb4xQPkcUyhL{theLe@y^HKu>D;xnJ#HhHiTz=tHoO6mr z*GVvDcmiv&^%c{tbzStnXSF^6lv1&6Ndai|0r=%+T+EiYm&@_JHtv80XuWsc*m3Nz z2h>`_1d#KlhKMm>8a&B)lLK2`6sK%r0R?oC{EV)`-z`884GBg7Fvj}soD*{ljUWD` z$K18Pj5Gl_;N_YOp_C@3+($#L&8>|W$uJCwlphizNJ=pD6Ij6N{>Kx_Tw(kUgJ=Oy5=ax0%P1a*0T zN-LI%xU+K(pJUhWg%I$O1<_?!zF(UjW8k{(^}f4c+mddm_x|#%IRK}24B24i*3iJUhjCcpwj#(j(dr4$S~5@H5{X7urL z`;-!Dt;_vqQ7~f8<+_}trOR-F>$)}NM&39l5S&xX@FkZ?;zZkROKXGtJ!~%*4Z*)i zMDJbEea@*M!V_hB;lg<`3^QPX7Y#>GW9k9$LjN&_ZZ^h<-Uqty3{T{&wPMT(CoF2t ziBgK5o=qbw=DTi^*0cXdYoF(Z5Z<(sV0T?NTI-trrsQRY0~uJY4Py);9ZjFZ=ko-C z6-pLd?faIu29Iq^%cc5&1K_<^>oY|$#t9sxh2Dh3d0rS}AWPawDZX+pjj#;7SXGQG zCGw=aUz_A9+qKYe%n)2#j6oTjHL4Fm!@!AwBT@7?f>?9naq|_2*L5p%VhSL5_1@vU zN6s61@3`gRG}g93k$r_p)YAl3ilS>yF~q)SIOkAn!)8vOF3$K(X=M@`V+d%Hz{D8! zJ{sKTJ!{$*{Edl^2ZsD%uFcg`VlAs>@p>0)`$D0f+v%M6;P=9~&1&Ym9Zn1fWF#b8(Cjtu;k)a%*zwA-sU$MWb0R0aSz#blrXz1>rVKNL_<A=ec$h?&n122TJF1OzP7Q-`&r~$RtGHM zf&|8Uzwm`GVD6(kb*YH~fP9=((Rr_@kn@Hi7{F?Wi`uq?i6<@5IBRj`m7lYzn}Y$p zr--PyWaCOzjjJ;}C?JrGq?GjF<`_%ySkx~9aH!o3F zLvOWXk&{*VJ#y~8XVJzsc%o@aQHchyln<*&QvZEiaB2?R_pF;`u|T51lcx(i=U#>d zpt==}WJ86h@4Z)DYs`s0#>;4=pxc%-e=|Kg0DUY>@+29oa6<4e-)Ewqc-Rq%f^!Z= zV9>Z_YM|C-1%?=-g4K21xUL(eRD3>%=475a^D^C(CwX5tKIWd@JIYcwm52~6DZl{_-yL(}GI9QBQ&AOAE{@_Mzt4OBuuCz9 z2mp%%lv2@p$0oQzU>btdWO#bob0X4mDMjV0+z5Z_!sW#&CA8Mnow;Zh_bdSDZmfu? zO|5&qVMb5P(^E)6dCJTje5QJlds-{x<@6$3s1TIB)tASdPtuqA)_K1`NeH<`X^Lm} zU9?u^%a3D67PZnA?Yy_vW|WoqifL||1p)<$CFqIxAnRdeMfc`&n7$UrScexvfcx*i z|310dMf&T*hv(B|V|)@ldH*f3vu#ORBGi%TvSSRfd{7+VsnI=cWA6W5tiyeH-t~*? zOIBfeVkQc~ui~Kz5$DUf5Q_9%JQ)_*7<2HH*jmy`e03#wicCrT+~Nd)Up7mS`+N=! z9U?hh*@Bz}JIXxkq?gB%?fF}PNf$4r+qNZa+omp>_Y8;;0B9O0;-V9*W`=HvuC>H) zuh!>j+ZM^si|+aTo=Co>*`&r7WW2f7$GLUiiu)Nig!^S(C%Mo%swNg=e91huR(!vw8k^zd@Q4I- zUEo3Bl@(T6qyjP}94YbeyQ_9injO6NaR2(}U+1#O{DrF$NC-BTz*81t9=54s%0k)O zWsKYF=joDrv*@?Fb}1EOjAtEr)0}Lteb1Vwr@;~K1ps_0Rgaz@lD%*vDW%0jHAO(_ zVT@7P7L)x)PqE>?Zp`^eN34Ub8?)QT{7IK32$9#1@x=07s-`na!cTwc>3*K)^`g7c zN~jAd!>glCcVh*`^h2_eQi8)Hzhr5Ab78<+rlpd|!hvN3@~mUdq3y}f=McuVynq5aqGt#y3GJAjq;Zb_M?R4sID2Lu2-JLLoiy4{=;ACX{~YS=}j z1Y~(M-N;r|rBrou8aBr8a%u8hE}X^=Az(_cIi;1uFH@UcTRuj<`^1W*m~Kf{UN$GLxthP&4v(^VcpUb2A;MpfM+D> zhvzNPU}1|@JLnzE>;CgOlquaJXxotnGBM|%iy(T^rELBtk~^{J;^yy@rm@(ws`b&1 z+0CHqTe`dDZDj}V0Ta6np{En!v<3jA-#k_&=Lt$q+;qiRRA38c+icMdvL|A(g zF|X@Zs!HaA9~BX$92OKQMa(hQKGN3PI}!_e|vpr@8i`rjm|k` zN351eLb7Ph%3@^brIv~iyrvY6718w;iI4`g_u7;my`#^mE@%b$<#y0YjRZ$~)e@g$ zPDCjU7pod-FuE;IoXx=A2a?2bWKB8dL~s4oDw(E}PWHn>Kg6Uc&1RN!UaO$ho|zhx z1S2Y;98x)s9pCeUbE_C=jb*48gl{h(vSWoIcsLg{t5|xw?@`hJ)orBWLBy>xVMF3i zAfe{PVu1IPoU{21ENaQ3oV935Nlq|okE)37?F3w`bi=_J%0f0vFfG!B2}(Ta5CTr4 zX}u~p20j3IaZ?rxq|D;b-@r_)_koAz*cDz5|dl){HrY5U|U}38*r9=n-R9Yq9XAL^1(Hndz{~gzXln zFwXfbS0e z4FC{R)GCIXqnx56!?Kt;hURr{?xzlIW3JXU61?RNy~{94ie!@7nWRX;-ZnV{5!_BH zd_KpLnDU0me6|{CUG*ZWiWJ*xZMwnhx{00qaVm$kgpmYbf#9*ixH|FrhZH_;7&C+!p= z^qjJxbyp85JdHLEe~ZG&McUpiKX=ZpO2}O6w4T(Y<)bjU5S}q907{O4l{P6y6}dl# z5QL39!kM++5D=3HQ%cEdiXiojH?2)m|KeZEuvq6LEr8cMzq0u`22MjGy>}`fS*eJe zo^`Yxa(UL*RO6yi;}iHDy+3Ufx{hsSOaPnh#08HdK6B0?Mzq@2&-P5@*xWehnwqD$ z06_V)cMik91$tImEGBp{do0grFDzi1WYdnJrW5ft&9K7Z-zm^FNlKg)@4b)JZBU7I zvG_qAX?B-S_^>iXxVp8n0!bHxSwtj7i8)JmukB7N%T$fg- zA=q629;!&9&fzuZyq13a{&K}9r?P&gjI^YqH5HWerliA;`O|nTX4r9{q{BZUutPTd hvjH8tlUVP)bSe4+R*!ewFa#<0DyB&zpJ&uS_^9p0HC#ooG-jy8@}HYF$Q?=007_b2>^J#HXLV$ zcMc(V{QUGjkW0asYs46Fo>||y?+If7=a~Tj+m>Lh!S{Q@T7zv%DCL6p9_N{1tbup3 zhuRu202si`wajmS|I6Uw+m>JqaOQ%2OS9RS|;6_j}^?+AzjI?;Y1w(YtJs2V9>8poac_Pi$Mld1ieU z0BUXMy~}d|u`cQhA}#vbB|}n=(@-l<8jd52d=9w&srFB zdkFwsSJh|eEn^IX;9-o#ahwPtptXkD8jLZ^`}EFWt${J-afYi_y#NmY$C0P|i7U{h z03fA+S}SU8FxFu7p_b!@#{hD<^rbmpaLyuxpoiYJq~35GS-m6Y3p6_4=e>h-7GnU{ zbs;4OJH5dejt?a%nuapV!E-5k!>u($>jkLBm|2D33-~QboDum5@i7MU8LtG2-iIERH*?HX zV6DLz12IN*Ip-YA3>AXhz9-~z!C8Yr448-I$9o5347|Vh6@o|a;{lYV%=q6DaxMrV zV8Ji!Kft%O4tt&j$C)w4P;?a6zR-I|?;W+ZN1BQ;;QKw1^M&9&F!SUXLs6H{hu|lO zh&vl&@P2LZ-m4+rb3=65TALWl81&w892wv5iBbwu3VNVgn|fAj9b-#Hi5*5Ryy zC5(#;4;W+B()``%1II)?x?yWg4@v8ZksNAk3M}963HO6m66}s6Pq&P^X#x}7pJ?1# ze_Y18?lEr}kWf-^ofpFOgqB*1l)`kW4r7G!y#1yasoG}(DMYPs%&X<#wr36KG0EqLZ>2|d?Wkq9#gy4ojxhhg5 zehfuyGsy%qiM2+H9A*>%2s7Kn7}Vf)+Y(X|MB7qSv?igD)Ff=_JhMVQp)ub3z*TSl{GK;%jKO(I0Yd^2f>+nvCtoK>tu9_$Ly7^T59FD)=#|b{ z*xozJRk5Wg^l_4UDJCp>@0#zJlUJ89#yn8F{QxsA$~;W^$e-LiW4yI4-|siFe?A97 z@QVK5@2Mas2elg8yTmDsmGmcEzbnPGwN?QoEg&|bXx^dLhSs{q0&{5y;woH_@h5KN z)E%9(h`}pk;@^%julj2(HU+yNl8SH<^` zygKGV_x;9!b#qKd^tRR2SAz(4mbmQH0!#Rs`)hXYIb|nbiH2nZj;=dY!B_tOoCnpJD4klU(x?AK@ z9_8DrX|10zh8}+9()?ZT{oxVx7J4UuDH2HOXq~g76V1aiCTp#l6A3_6Jpd^zwk=`b z1<46;EI`KS2*FPRf6z0ogA}|Yo77N_S`J?ENaJUW24ShZb08D~$ zmxbJqb5?b&-Un*CZ~XPzl!CHWUvs}PKy9MaS-X%7(l7E;v2dM-zS$9pf|;tt>P{&I zy#;Dr=OQq8?-lTlGvhonO1bcU@0yc&>&GIR?6YjBp%~y+sxh%$3F%y*V12w|nbY zf7Yew$mLQlP|Ag?3U?+llk5;c*G2&_;AuJPMmZNf2!HCvrx(T;P^;)!`<7Jk6qLLd zqr0&p;u=y*TRqfzU;xPZR0>K7mcz$jLmlOz;l<3|x zG}Bi~L36kFP9z>4)>@+i7+ru(HpV9rm(S;1(3<2~wnV5S(`7{r91r)Uu1$lljk*8t z!#dpBd#ztxe@vMuYA;NH)XELB65`9w2_`K3xp`xxqd)~wLLq3LrAW?0yj~kx?-;c7 zjQbUc?wfxec8sCOO;7lI4yC)Ra7RP%S^myiix}mSlzpug4jW+<1yYI+JqQ>I^>L;F zsiqvpv}~J5xhfpaT9t$(&WCQ`|GLrR$SUwPO)m!T_l`P4gw@hYSg6I7a?Sd?F6|fh zY?o5}X1;^QElGnr1pknE zO1U1)iRC8K+u9Qb*wPi@z&ShHHPT2QqiaK*MrBtc#-Q23ISccz-~V%nYCnGAsss{( z&9)`vQV^HIyw>(hAwT9ue}prIOnuXln&?fon(W3qWZc9 z>8hoR^rcNg_ChH&#xRQtGc5|Ghaq@nTYkzr?>!r6bnCt1FM$?PLCAK4$*q z6u=lm#utpinJ<-X_B|m4kCXkSWjf*eJs)(}uM(;trMs2;xv4R`a8wSMqp59ieu7@0 z?r03Ueaf^H_1N=a-e)`qzm%4*?a}ezO?|&7f_F+{jw5S(HZT3BbykEQ^T%(08${{z zl=4pjW*(Z9ueGiy%#G0f$ZS?XTB{yn0eeJiONA|xeM|b2dN;X&>QzTp{cY_!{wxXC zoWC~W?b;~^piQbV2HrDgl9bmfh)dS9wm#@aoT{P6P|D?vdG8)B&CSrO{LBc>IgHUC zMmmnWXduAxz2{l5ZBccxb?$?uVrxy(60iMd(+s#Ppq~J-7X5$r5yN?$XL%g>sVF{0 zygwV2I?jx+H1$%9+Rk0mAhoY`{)Dbf7ti^EWlwkC6H<(-ZSfC8Eb9}v?xLCnKZUc1~-%1_owHHcxY$e`gZLY!>j}+`+C3k z=|1u@5_C4K$i$3+Zi48^>S@j2bWvl(qKrQ&K+MBfq^@*f=6yB-XdE`=VvJLjvB1m? zR8izXv#m;IM5-#SP8n|b z>GsU60J{<$DMp!r1>6AUbPym#<2pK-T7OSzPU$cFFA zs8&q3zou>^vB9yTGH10Y$Xjb*ot?}{`n$DGOI!00lc@0ET@HW{X8N>;s}NVP-;%ZB!q2}X6Q z5o=ZTnZKtjOO-|3yFP$)9V=X0Ky1&jR_6OhA4spI+epQOh+B$qD7XRVIOwzzTXO2$f)T4J&PLLQuRnycAUPIDab zJPWR?Vw?If(U<++)|wXgsxoRz*3QEe^#Fn$FYBS7wT@$E6Ym}NoyCWo!;e0u+almk`nCq%40=(ZlaxSW6tt}Rc5~W-Z zM5i`JCB|Br2mP}uX$MaIhqD%O3*h9e z#B09+-K*PE)udhqKzaFla`w;X(76$M#&O=1kyG#NIguExB!yVP9DdGn z7r?D)edhc{pDxaP+FI8aGoDtFobgWN?L!Q>&oSiH+F>HDSq?*Ql7@O$fLyy3bWH&= z|437-R<;`QoYh;JgF1j*jAf#hnpPI%hyweoWMO<6M--V#SkA>5mXTrE%ozRAwxklW zPT#CCCxW@SeQRBxXYs?%z=Ns3vW|&p!mQNkku8LAs~5K)tFjUjw(f-_s%&T{QBuCWGw^+;8IG- zYHA%sr9{honWo#PBqI`(j_Bd*nxF4EhtxX0-0uv4H&FIrkWH;X7r}r91=- z&%m#(Y0Q|Jz}J75Kn;l!ErmV0trYVfwbuFbp2%5qV;=)4MimzTD4!Ovu@9rO(lW+A zMjMC|0JOkhyM{NsrdV>p#UKSElC>Meh-*u8QcEB0isE0$+F>`1RTl{M}p5xqx*%{|SL2@~p)<2um`> b8Sj4sJ%r;G6~R8400000NkvXXu0mjfnlArE literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_15_ffffff_60x60.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_15_ffffff_60x60.png new file mode 100755 index 0000000000000000000000000000000000000000..19ab46c3f875f352230abdd8a2a651003b8b83b1 GIT binary patch literal 3716 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 Qe{7QJJu=d+)_fWIKRK1dSpWb4 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_68_b83400_60x60.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-bg_fine-grain_68_b83400_60x60.png new file mode 100755 index 0000000000000000000000000000000000000000..3f3639b490c47377b4a7bf6f57d52883ddda9cce GIT binary patch literal 8306 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

    ! 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 R#}izLrLL?4d#h+0@gK7EGZp{< literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_222222_256x240.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_222222_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..b273ff111d219c9b9a8b96d57683d0075fb7871a GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_3572ac_256x240.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_3572ac_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..6127bb0c84991c92d688df5e5d65e4fd9f67ee64 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_8c291d_256x240.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_8c291d_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..961c8fea3bd431af0918d53a6ff55f8f8bfd014e GIT binary patch literal 5355 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_b83400_256x240.png b/web/css/globals/jquery/plugins/ui/pepper-grinder/images/ui-icons_b83400_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..7ca9d0b84c359a3b0d621f4aad50c0d5b2ef58a0 GIT binary patch literal 4369 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 paEc1Dtp8Y$jK(c1^@s!&CN`00RZ6f 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/pepper-grinder/jquery-ui-1.8.5.custom.css b/web/css/globals/jquery/plugins/ui/pepper-grinder/jquery-ui-1.8.5.custom.css new file mode 100755 index 0000000..724035b --- /dev/null +++ b/web/css/globals/jquery/plugins/ui/pepper-grinder/jquery-ui-1.8.5.custom.css @@ -0,0 +1,572 @@ +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=6px&bgColorHeader=ffffff&bgTextureHeader=23_fine_grain.png&bgImgOpacityHeader=15&borderColorHeader=d4d1bf&fcHeader=453821&iconColorHeader=b83400&bgColorContent=eceadf&bgTextureContent=23_fine_grain.png&bgImgOpacityContent=10&borderColorContent=d9d6c4&fcContent=1f1f1f&iconColorContent=222222&bgColorDefault=f8f7f6&bgTextureDefault=23_fine_grain.png&bgImgOpacityDefault=10&borderColorDefault=cbc7bd&fcDefault=654b24&iconColorDefault=b83400&bgColorHover=654b24&bgTextureHover=23_fine_grain.png&bgImgOpacityHover=65&borderColorHover=654b24&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=eceadf&bgTextureActive=23_fine_grain.png&bgImgOpacityActive=15&borderColorActive=d9d6c4&fcActive=140f06&iconColorActive=8c291d&bgColorHighlight=f7f3de&bgTextureHighlight=23_fine_grain.png&bgImgOpacityHighlight=15&borderColorHighlight=b2a266&fcHighlight=3a3427&iconColorHighlight=3572ac&bgColorError=b83400&bgTextureError=23_fine_grain.png&bgImgOpacityError=68&borderColorError=681818&fcError=ffffff&iconColorError=fbdb93&bgColorOverlay=6e4f1c&bgTextureOverlay=16_diagonal_maze.png&bgImgOpacityOverlay=20&opacityOverlay=60&bgColorShadow=000000&bgTextureShadow=16_diagonal_maze.png&bgImgOpacityShadow=40&opacityShadow=60&thicknessShadow=5px&offsetTopShadow=0&offsetLeftShadow=-10px&cornerRadiusShadow=18px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #d9d6c4; background: #eceadf url(images/ui-bg_fine-grain_10_eceadf_60x60.png) 50% 50% repeat; color: #1f1f1f; } +.ui-widget-content a { color: #1f1f1f; } +.ui-widget-header { border: 1px solid #d4d1bf; background: #ffffff url(images/ui-bg_fine-grain_15_ffffff_60x60.png) 50% 50% repeat; color: #453821; font-weight: bold; } +.ui-widget-header a { color: #453821; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cbc7bd; background: #f8f7f6 url(images/ui-bg_fine-grain_10_f8f7f6_60x60.png) 50% 50% repeat; font-weight: bold; color: #654b24; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #654b24; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #654b24; background: #654b24 url(images/ui-bg_fine-grain_65_654b24_60x60.png) 50% 50% repeat; font-weight: bold; color: #ffffff; } +.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #d9d6c4; background: #eceadf url(images/ui-bg_fine-grain_15_eceadf_60x60.png) 50% 50% repeat; font-weight: bold; color: #140f06; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #140f06; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #b2a266; background: #f7f3de url(images/ui-bg_fine-grain_15_f7f3de_60x60.png) 50% 50% repeat; color: #3a3427; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #3a3427; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #681818; background: #b83400 url(images/ui-bg_fine-grain_68_b83400_60x60.png) 50% 50% repeat; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_b83400_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_b83400_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_8c291d_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_3572ac_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_fbdb93_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; } +.ui-corner-tr { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; } +.ui-corner-br { -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; } +.ui-corner-top { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; } +.ui-corner-right { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; } +.ui-corner-left { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; } +.ui-corner-all { -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; } + +/* Overlays */ +.ui-widget-overlay { background: #6e4f1c url(images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png) 50% 50% repeat; opacity: .60;filter:Alpha(Opacity=60); } +.ui-widget-shadow { margin: 0 0 0 -10px; padding: 5px; background: #000000 url(images/ui-bg_diagonal-maze_40_000000_10x10.png) 50% 50% repeat; opacity: .60;filter:Alpha(Opacity=60); -moz-border-radius: 18px; -webkit-border-radius: 18px; border-radius: 18px; }/* + * jQuery UI Resizable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; }/* + * jQuery UI Autocomplete @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_flat_75_ffffff_40x100.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..ad3d6346e00f246102f72f2e026ed0491988b394 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnour0hLi978O6-<~(*I$*%ybaDOn z{W;e!B}_MSUQoPXhYd^Y6RUoS1yepnPx`2Kz)7OXQG!!=-jY=F+d2OOy?#DnJ32>z UEim$g7SJdLPgg&ebxsLQ09~*s;{X5v literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..42ccba269b6e91bef12ad0fa18be651b5ef0ee68 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouqzpV=978O6-=0?FV^9z|eBtf= z|7WztIJ;WT>{+tN>ySr~=F{k$>;_x^_y?afmf9pRKH0)6?eSP?3s5hEr>mdKI;Vst E0O;M1& literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_glass_75_dadada_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..5a46b47cb16631068aee9e0bd61269fc4e95e5cd GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouq|7{B978O6lPf+wIa#m9#>Unb zm^4K~wN3Zq+uP{vDV26o)#~38k_!`W=^oo1w6ixmPC4R1b Tyd6G3lNdZ*{an^LB{Ts5`idse literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100755 index 0000000000000000000000000000000000000000..7c9fa6c6edcfcdd3e5b77e6f547b719e6fc66e30 GIT binary patch literal 101 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_2e83ff_256x240.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_2e83ff_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..09d1cdc856c292c4ab6dd818c7543ac0828bd616 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_454545_256x240.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_454545_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..59bd45b907c4fd965697774ce8c5fc6b2fd9c105 GIT binary patch literal 4369 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 oBo%DI*Kv;w;*%(i9W@f3_WCF#rGn literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_cd0a0a_256x240.png b/web/css/globals/jquery/plugins/ui/smoothness/images/ui-icons_cd0a0a_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..2ab019b73ec11a485fa09378f3a0e155194f6a5d GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png new file mode 100755 index 0000000000000000000000000000000000000000..64ece5707d91a6edf9fad4bfcce0c4dbcafcf58d GIT binary patch literal 251 zcmVbvPcjKS|RKP(6sDcCAB(_QB%0978a<$Ah$!b|E zwn;|HO0i8cQj@~)s!ajF0S002ovPDHLkV1oEp BYH0uf literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_flat_10_000000_40x100.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_flat_10_000000_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..abdc01082bf3534eafecc5819d28c9574d44ea89 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQY-ImG zFPf9b{J;c_6SHRK%WcbN_hZpM=(Ry;4Rxv2@@2Y=$K57eF$X$=!PC{xWt~$(69B)$ BI)4BF literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..9b383f4d2eab09c0f2a739d6b232c32934bc620b GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnour1U*q978O6-yYw{%b*}|_(02F z@qbE9)0CJMo;*v*PWv`Vh2h6EmG8IS-Cm{3U~` zFlmZ}YMcJY=eo?o%*@I?2`NblNeMudl#t?{+tN>ySr~=F{k$>;_x^_y?afmf9pRKH0)6?eSP?3s5hEr>mdKI;Vst E0O;M1& literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png new file mode 100755 index 0000000000000000000000000000000000000000..39d5824d6af5456f1e89fc7847ea3599ea5fd815 GIT binary patch literal 3762 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png new file mode 100755 index 0000000000000000000000000000000000000000..f1273672d253263b7564e9e21d69d7d9d0b337d9 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0l%l7LV~E7mxPQ=F85a&M@g_{ d|GeK{$Y5lo%PMu^>wln`44$rjF6*2UngE4^EGqy2 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_222222_256x240.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_222222_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..b273ff111d219c9b9a8b96d57683d0075fb7871a GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_228ef1_256x240.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_228ef1_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..a641a371afa0fbb08ba599dc7ddf14b9bfc3c84f GIT binary patch literal 4369 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 oBo%DI*Kv;w;*%(i9W@e{{5C=l}o! literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_ef8c08_256x240.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_ef8c08_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..85e63e9f604ce042d59eb06a8428eeb7cb7896c9 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_ffd27a_256x240.png b/web/css/globals/jquery/plugins/ui/ui-lightness/images/ui-icons_ffd27a_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..e117effa3dca24e7978cfc5f8b967f661e81044f GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/web/css/globals/jquery/plugins/ui/ui-lightness/jquery-ui-1.8.5.custom.css b/web/css/globals/jquery/plugins/ui/ui-lightness/jquery-ui-1.8.5.custom.css new file mode 100755 index 0000000..11ede67 --- /dev/null +++ b/web/css/globals/jquery/plugins/ui/ui-lightness/jquery-ui-1.8.5.custom.css @@ -0,0 +1,572 @@ +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; } +.ui-widget-content a { color: #333333; } +.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; } +.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } + +/* Overlays */ +.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); } +.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/* + * jQuery UI Resizable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; }/* + * jQuery UI Autocomplete @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/web/img/globals/colorbox/style1/border.png b/web/img/globals/colorbox/style1/border.png new file mode 100755 index 0000000000000000000000000000000000000000..f463a10d838aeba263ff44e5c3578dfe7ba07648 GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^j6iI}$P6Txn<}3KQfvV}A+A9B|Ns9>Z_d99WHFWm z`2{zujf-{zawI%m978JNrXFPE1@f8<7E8}%%lTu>&#KnM&G`7uteHSj22WQ%mvv4F FO#q`M9f1G< literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/controls.png b/web/img/globals/colorbox/style1/controls.png new file mode 100755 index 0000000000000000000000000000000000000000..9257176616c3e0a5c145e381a12ee15fa556dc8e GIT binary patch literal 1249 zcmV<71Rnc|P)V!Y*4EZ4OogST zri6rqYJsRyQd08t_V@SqUU#9bt*zwf?~tLsnVFe+kFwX@=UG`+oh~W<+9? z#KpzEy}gQxinG4bHdTvkgR8Z*wRDQE#?IZ~l^To_n@2O}IT3~G6)PQMiWkECPLz3lbBk=|vuW1?-!1MPfMnI7-k<+5D4+u9dB zmBqs*!1`WX^^eNcMJr!PLIM#5ImikCM2QFtu!&5!*FQ47ix$41yk(NoRGKV31w{bT z00~7PN)uuam~M@5V_#6h?wu403Ltp{i3MyzMz?=r zx{Z9j{bnVTaFT4o&?6v~XF->~u+!yjSAg8%Bz7`BuBukl8fm*EyoWnQWFSLX6e6vze7N{Ty7V&(` z013w{f)568-|HJ)$j$ZN`bGpC*d|nVVfcw7FmAqrV&F{aP zAfRO!1jYNWFghE|S5dkD%AoieT(?fR;rE-8)x%mH;NNd-i>~#7T(|MH8c;e7fRb;Z z=p`-S6U_JKknc@PUwdjST0t|wU3eP$GGPzp{xmDGa{g7q0HG}46K;LARAd7PK&#{c z7+@1EeBD34Iq+@#7|bUe`nJ!1)AxU02`UT7WBL+4;r@Q+0V0A|Q;Qs)aP5nw_5!?)*?^0x3>g-<$cLB zU7pQm9~?h#Dm0rIMbY~a>mTSc<)`naIj|E?j~RHs_RrLpETh4iq_}Tp>-+wF8GOiX z37n}fnck7qjP)fy|G56er~EN0gXD+b*EB&nPhaxM_t*57RBMrsgTa8j?itWiave|{ z^nJW9nfd0t1p6WXoOf!pl;OSW(@yK~uB*MGk$ z1Cqb$OGamSAE1%7A6QeQ@CkJIzC}aPD>IDKD8L8Uwp zD8N$+>xvXU;oyBNH_mf>Un0zeaPz*KM@m^&i|`2-zbE+j|6b!y_Ir_OkE~-m00000 LNkvXXu0mjfA+dCQ literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/internet_explorer/borderBottomCenter.png b/web/img/globals/colorbox/style1/internet_explorer/borderBottomCenter.png new file mode 100755 index 0000000000000000000000000000000000000000..0d4475edf4e1ebb18d41db83915a21a9a6bc5977 GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{P$P6U+&JlA5QfvV}A+A9B|Ns9>Z_d99WHFWm z`2{zujf-{za>PAd978JNY7Z)cbT)5ruP-ra%e~G}wTijXxbw&apdf>%tDnm{r-UW| DknJ7# literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/internet_explorer/borderBottomLeft.png b/web/img/globals/colorbox/style1/internet_explorer/borderBottomLeft.png new file mode 100755 index 0000000000000000000000000000000000000000..2775eba899d10d3dc6f8c4044ad69cb89a5f8499 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw|3?!p1cPs@`>H$6>u0R?BQd3j6Z{H4KpFe*d zCJdtg|NlSbL?0`V#ZwaG7u>*5G5>gk!F7ua0ST$+oA`j@!JaOTAr*10r#A97C`hm- z3dKH%oOr{aLpj2LsW&>nlF|6W##ZJ(&Vq|N!)H`n4WCnOwE6XE$$JkZ%dBmlKK#!l q^N>YH$6>u0R?BQd3j6Z{H4KpFe*d zCJdtg|NlSbL?0`V#ZwaG7u>*5G5>gk!F7ua0ST$+oA`j@p`I>|Ar*0Rk8R{_P!M3f za60r>rSy%6n-)#V7EE~whgkX^aO5~8RysR6J&4z+ys8}98}g<84F4bXKSykNqt5CV sw|{E#ePx# literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/internet_explorer/borderMiddleLeft.png b/web/img/globals/colorbox/style1/internet_explorer/borderMiddleLeft.png new file mode 100755 index 0000000000000000000000000000000000000000..a2d63d156e008a01fde9d2d68c531c34926a924a GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^qCm{Z$P6U8=aokQDYgKg5LY1m|NsA`H|O64NtXoq z1vjjXi*^HYL_A#_Ln`7}8#ZzRc@9(l|2O5zVPsZ_d99WHFWm z`2{zujf-{za>PAd978JNY7Z)cbT)5ruP@Q*%6-Zwv5L7-WU|;#pdf>%tDnm{r-UW| Dg{~b` literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/internet_explorer/borderTopLeft.png b/web/img/globals/colorbox/style1/internet_explorer/borderTopLeft.png new file mode 100755 index 0000000000000000000000000000000000000000..f9d458b5bba4a8cb84b9ee39a3844e258c1dcc20 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw|3?!p1cPs@`>H$6>u0R?qNKH*WfByXT?c0H} z|NsBDZcF_MWbu>)`2{yHR2W1EWLV67bmi? zvo+on5)g6V?RfL6QIf literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/internet_explorer/borderTopRight.png b/web/img/globals/colorbox/style1/internet_explorer/borderTopRight.png new file mode 100755 index 0000000000000000000000000000000000000000..74b8583cfbb2e62a84ef04ba01064ecb4d70e72a GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw|3?!p1cPs@`>H$6>u0R?Sq^70<1<#*9zkU05 zAmji4|I_}5*Z^5PB|(0{4Ga|#78wHb4Xz)Tc+|OiK2SW!)5S5QBChw?MqY;mf#VnJ z^}ggWcLpW1DoZ@PeSjlp!DT5|UGpg|kLp?vt)3@$_tm@JIJ0M=)Av-}@ro0Ud)StJ qkbTcp`HhbYABf8t?|bZq*G4@Nf;!!};89ZJ6T-G@yGywoYi%4?- literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/loading.gif b/web/img/globals/colorbox/style1/loading.gif new file mode 100755 index 0000000000000000000000000000000000000000..b4695d811d4f84ed693eff7b5e6b4d61a3e8c48d GIT binary patch literal 9427 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 literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style1/overlay.png b/web/img/globals/colorbox/style1/overlay.png new file mode 100755 index 0000000000000000000000000000000000000000..53ea98f7003cf014cda2f764a7d982e405bd1b5f GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^av;pY3?xs=ZJr3E3<7*YT>t<7&(6-y!NDOSA|fs> z4iv@@^0LaF09818x;TbZ%z1NeAuof1z|jlq0?ZV)A8h5lQLnD@Q>uGq<+3e(rH8NF wGFLG_IXT618W25kEc*C=-akH|?*IRp8MKNmPAJ`FsRUW)>FVdQ&MBb@0FX~HaR2}S literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style2/controls.png b/web/img/globals/colorbox/style2/controls.png new file mode 100755 index 0000000000000000000000000000000000000000..8569b57f1023685883cc8b2002f0763d4e79638d GIT binary patch literal 570 zcmV-A0>%A_P)P)t-szrVkq zpP%pV@Bjb*hlhs`4-Y>-KYxFJ-{0SWfPer10MF0QmzS5fx3_O^Z?CVf=jZ45_xCR^ zFQ=!ckB^VX$H!k^U)R^yPft$(vqlU600F8=L_t(|UhUP{YV0r!08lO7oqd+}|G)Hl z!i3VX%tTOnA8I`$U=bY@A_e$=M%TN3lY!f+1HvLxga~{?BuQU~CFD&cP&}kihULVfuoZsnL`Q~xuv-(kN1b~-H0OM{*xPj(Z zH!CNOhW5k_q@1|SN}boap^M|QnCG?;dN+Z!dVAB#n~UhURSK~f1n|Kh0M!3+UH|Rq zde^(&^{WhANm(+>RB+Do)wv+)v?Uoxo50g%A4s-!(kB#kNXfBF2?Rac)Y4OdYNDL( z4|yRqDRnG(a?X8zNABgQB&u$9*W%-n1wDO|M)&E6m%fjfBtF&nQELn5^(nRVcYJ19 zPd1#^{m<*$d7bf{j>wU@?OCGtb_Kzxa>qde^)D4;WrrV64tv9smFU07*qo IM6N<$f+}YfD*ylh literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style2/loading.gif b/web/img/globals/colorbox/style2/loading.gif new file mode 100755 index 0000000000000000000000000000000000000000..19c67bbd0403f3f00d71bfb21a59cb6c55d482ab GIT binary patch literal 9427 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 literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style3/loading.gif b/web/img/globals/colorbox/style3/loading.gif new file mode 100755 index 0000000000000000000000000000000000000000..19c67bbd0403f3f00d71bfb21a59cb6c55d482ab GIT binary patch literal 9427 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 mH0g4?YGLBn8=HK2#}DSr z1<%~X^wgl##FWaylc_d9MUkE^jv*Ddl79UEZ_g}Xz+@;aV9adDEwGu{P+Z_M<3Eh*0;`!f{yW+&p8kKiV8!omZzCtj2J9ADP{!?i_YiBTH20OY3g*U5e;AT@xD8%S fJjlkt#KZ8aHT9v(o>xggOBg&|{an^LB{Ts5Ca*dL literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style4/internet_explorer/borderBottomCenter.png b/web/img/globals/colorbox/style4/internet_explorer/borderBottomCenter.png new file mode 100755 index 0000000000000000000000000000000000000000..12e0e9ac022d0d48ea77f547c51b60b240487b77 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{O!3HEhOmL|OaY|exN`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%S$n!ThE&{2`p3`W!{gu}_3_=^-Ng!Rb{)T&UH-HP?{_$o ypQdn-M`?xwi?Rn#&c%9x>5Y;Zy37s@j0`{Q1aDn?T4)6{hQZU-&t;ucLK6VDcrWMx literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style4/internet_explorer/borderBottomLeft.png b/web/img/globals/colorbox/style4/internet_explorer/borderBottomLeft.png new file mode 100755 index 0000000000000000000000000000000000000000..b7a474ae056f3c500e460f6d88bc1621053ff219 GIT binary patch literal 473 zcmV;~0Ve*5P)P000>X1^@s6#OZ}&0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzdr3q=RCwC7mQPB=Fcik~V*QUuMWmvL z3tf5)cRi1ruFH4^FW~{aMSBGo-4$GS@_dn|Jj2T*Z8h*CFfiZO?`vLW6e22GKZrdG P00000NkvXXu0mjfah1x1 literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style4/internet_explorer/borderBottomRight.png b/web/img/globals/colorbox/style4/internet_explorer/borderBottomRight.png new file mode 100755 index 0000000000000000000000000000000000000000..6b6cb159b92b51b1bb735c359f2db599559bcf2a GIT binary patch literal 470 zcmV;{0V)28P)P000>X1^@s6#OZ}&0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzcu7P-RCwC7mCH)RFcgN9SjQWKC?XX> zTXXid{RGsZUH z9^8NuoP#wu0xM7eVc-}xb#j_AWVKw0D^rng@+CqDJy1H5=<2d0oO2ZrDBo&+x)K=> zsb8(6@B24AMoT19LY6EUhT*-cs#iynmYdWXsq6X?pWm7KxB?g86r6xVsz%KGX_Bt% zUQ7Yrf@^RI&P)*=g9ES+_AJ375wQ@V%Ad-ze9*5JqZKac3Dxpmv~9axwi2ag2OBVg2uwB(bJP$H`D zhKUT9mV5;yZiPp~Ly2q&^As1gi=^i?_9lK!#8M>^4V6wjH#Zs!h+=&fI=5!YDsbV1 zCW{JeNfEl$LY16y70(ipc-^v`$$b))Daw-YznCm2na=xvZr=h709CMu`cYr}PXGV_ M07*qoM6N<$g158F@Bjb+ literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style4/internet_explorer/borderMiddleLeft.png b/web/img/globals/colorbox/style4/internet_explorer/borderMiddleLeft.png new file mode 100755 index 0000000000000000000000000000000000000000..8f248ac12f7032bbd5a4d286eaa423c391935358 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^l0eMD!3HF&7fw+F36!`-lmzFem6RtIr7}3C!be$JWM`3B@Bm;3pFq>GaR@h&Z=h^><2V~!PC{xWt~$(698{IEJ6SP literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style4/internet_explorer/borderTopCenter.png b/web/img/globals/colorbox/style4/internet_explorer/borderTopCenter.png new file mode 100755 index 0000000000000000000000000000000000000000..7cb1da43ea70d16cd9e8a5e04db63615353ca7dc GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{O!3HEhOmL|OaY|exN`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%8G5=nhE&{2`uG38Ju`=aQKHb9f+kj%4NV+o0tfljEoSJ) o?YVJ?!{n)b$7u&4Mu7wdwi{f5KmC@612r>vy85}Sb4q9e0J!ce7ytkO literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style4/internet_explorer/borderTopLeft.png b/web/img/globals/colorbox/style4/internet_explorer/borderTopLeft.png new file mode 100755 index 0000000000000000000000000000000000000000..d733b6c866fa5567eac759c40bb656e32d6a2e6a GIT binary patch literal 405 zcmV;G0c!qP000>X1^@s6#OZ}&0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzH%UZ6RCwC7mA?*xFc8MM_F^22hB&ae zI5_YiZVWzyqp!dNnD}@+1*WGsgG05MHKC@A?-+eQb9V9j-*r5H21FSoX$hOzpf-2n{QVV`&ZA(_mV5gA~6%SmjD9*9!EP000>X1^@s6#OZ}&0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzb4f%&RCwC7mQ7B>Fc5`1=C}MRfS`y9 zfdosI+#)ApA8`jR!6rKnkbQCm7THCtLu1C#xN--N6PGg5t1UO3ukXom0$S_Ie;1J4 z+9=hSFaR~Ox!wdM2B2dh+XGE?#CIZOHvo}FX-$Mo1AsB}Ajm?F1Ark7f-K~WLAW|L z+W8~^`~{f_0Jk}xcO#swKyCvNtA#N4Ia>i)$fF0s0bCM{Z;U16$ysz`;f=r9xCq3z zbkjqss(P~vN+}Hh+Kf%pd}dkpk>`0sjk&lp--hTR=0mKuZNJL0e5FY5QFGK4>Kt{7 zIzsK!4Q)=2Ejp&ajVlPTRoC@9f$mT@s7q8r_t*hy54FP}5Ct(sjvi5Y`Zw^&kbx{fLNXyN2~I-9fRL~W0YO6u5M?m|q=?9_f+C`VVvlXZgg_P` zVN(!LHWhcVE{Gc-Dk>^?tcNPr2)1rrw6(4Mw@>>Fp!Vq-d(LyN>(5KC3txQr-rxD% zznO>y;i7i$H&JXJ$m%pZ@=BRabx%H-3JdIymjl=>({Tp z{r21a`}cQrbeuVJ=9_Q6X>4pPEiK)%XV3HJ&wu>!$M*L2vuDpGFHr(0TDMn*;+Jb18f-MWH; zf`Ng7J9qBfyLa#V@4q*hOnG^ESFc_j9v<%R?|=2`RYOC=(W6HvCMKRed$w=iz9mbR zBqk=VSg|4@Az{s$HQn9aPoF-0{P^+9moG10zI^J`spH3wS5;LVK79Dtv11>7^igqf zaY;$ZmMvS9N@Z79*Vx$DC!c)s;fEjIzkmPs?b|Aq>coi?si~>i+1YE?u1!u(PD)C; ze*L;$uP-YrJAeLsQ&W>pr>m^2tgf!EsHo6rG+9|$nVFgC>FKLht@`})&p-R@v%0#v zYuB#T*4D0Gz52q13(J-*GZ+kMX=%;P%{zDQymIBr%9SfSJ3EVtiVht*w0QAi;IBWN z-yK5NFHbL6o`r16Dac0BSEU!_u1(JpJoB*pKlFHVRBT^oWQxX1`^edYm36% zxxsUG7|AyFcJ|f)>~L^CO7<6nl_1T*-qp?<@C%y92PFIvfr0Mm;O5y5;8`ergby28 z&JJ9kQi+f+s^-o1>MLc}8%Qu4s`wv0Xl_a4)l#>00!AF3z;(!P34tgsiF_{Z&{8f) zu*;SXOKgs1+uAT>rr!QCIs!9#Ll8~BlmvGS-M$%SA%#-{-Fk5s^*L(U0W;durN@Y< zcxV6@^Oy^cq@>XTO<)LCnH7iK10AjUZp$`goCMgiK z#lQ<+7$rYSicd&d)`D^MltU4b(Tb=zVDSe*DHzv1fr0E2DR&^CvAY2JJw99`c8Xlc zoZ@PgczItdVf)q~fd^I%2o+>*nDKy}W=d;xW}69VwVD+RSIIAj*l0MmlBDid*}W5$ zIwj8&na~1ZP(GEwU00<<4>Sq2j`U*5coz4 ze4kV)FJ?Y$sGtQcx2)5%BZF2fL>g5RZ@$VmI%s7SsU1KFX_=uYJAS{s4Pe&^Qq!~2 zm`AeqCqpEfj~J?iq#2pZmbnA-=Vi|T{4zZF0t3%7BiTj7SP_F8_RDVc{f+A5on!4`yt6bopinwAmV+4z3v zXM-1&Odj4wSqAr_jv;tBs7Up7OGzLd7p0uV%Nu}_zro^!Nu4dmCU9yJ&ED^{*cx*p zB+iSd(~~k2^Oj*wkbj5@&PvQ)NNPuCr&5=q?BxA6Z2*OBfc#P&9diLBe1AAZvdXg& z8JJF~tJin{*7kO50a;RTew;xt--()G4L}|_=$hc=5x#X~>6f!gBNC-I8(~fkWJv_L zhhuTLO3)j8iC%1p%UQ3j@iCHks`^ZDd>U?Kdh9+}`*!T`??sZ>hO!M6G6gpbLt3q{Uxzf7AS4w# z!@)jncRN6l#;ih{9o#b80J5NtNU*RX0tnT?&KV%lToSXK(UKZ=Pv@8b%Kci_fo(7x#@5tn=dRwEYH}C?^EDZ^t`d&uBoy6$#s2k z8<*sulO-1Tjf{tdN&NfWMrzv7W>xH<&Y$gycq&T}kg@q$4^EoB3$%dl4+iqxBLSI$8nm<%(01k$CayL| zp8U2`{3b!rdvOG_b4iAZnKto0b$Z0bCfXTn)Ur41%s@~?NU2+t%STBCHuBYNc(4+|yDw#xhJCPn zHE%n_BbV`$Uz;TH&Q#HBFK&pkwH1&9t|;OtdUprcPeafe#FGF zv}2}A6=gb-2?30HjQz}D1LuE+Vd~r?r z3`XjV1|2w`LA9&~Kw>UdKN4RQl(t(`OExL&WrmPN91#FnGv-7bzeAQgh_ixpA8WpUGFmS!=IU&PZh^Pjv*=X4?O1o8e8h|r-&tZu*tet| z6ntUdCVm)KjPO$k?o!ZU_X3{eL^+QTF}TCvl(^;Pz9$unB@%0OgvopkfOmIVOzcqJ z4DlbZ_Kw^zjMrOG?%BGDGxm?2o;(NW{i$z~M0lT(!B|Gh;gG8IaWYHqZCWUF`+Num z-M!gH_ErF7$^!gAKoDUQJ<~uJnn?htP`0TXiHVm-QID2G{(Qa=288noLLhWmbd-aS zL41QxL3!g4?QRjH6Z5$#C{2kfa(ZEe1S6&FrG>uD<>II(7iEOWNej+a6%!l7Ky&4< z+XhQnvlD8Sa(G=E*KvttRdUvga7h?tK$G}A)zk{YwE_JnZ&0C+en!vuNXH=(QYBN) zB}(kA^F@xq`9F=P-gN9NQGR;4veydhYLVIGq_Nt$YH0ZD$1PogSt92rN98zLJn7zK zA04`X>fJMq_xi|5eAey-MX@rTAJHn7yc zfHbo8xecNA0YH_RONUWM5irgwP<@RY{~x*w?|I;-7?kY!)S#uFd*?*@8z%&;DwiyB z=hf-(Io|Fnh$XH*V5LkO=)HX!FuI0GjPWU2SccHj;Rl(}7it-i)ON z(M8ONYTD5XvNKY$ULCr?p@x=@ zmc%0(FJ>t#d&JxPi8JjEK899nu5)60A;J6=)_^w2Pi@!RUPjrM$Rf_;?Rt}DOBkrwx56dz#@lDv*^FUw*gcpaPZ9I?Efopj(x z@hgBKp$YSvLfg#tn#mARb?0Y90svW!XvHCY)L>P?d|AA5QSn!`NVs~?nk^Y8d zACKc*m|I^-d85L*=+kWgF_e93QQ-{YFxEczN<|?LcWNMC9&b=PWkQx(IJwBgx-Pyi z{L$!<-4!9c-?u_hkJ5(fJ+0U9t2edp;L;+_ZE46fE9;S9TWqH9p=}(}T0#AmctqyM zEUoPB^M3r9Gwqr?O5iKW6yJGI_(dJ=OT{$dxg$7biG^WyVoVSiVlpQt@`H@7EvCqZ zJtr zu*Wo0K$6*EF@7@+Y7g^$bzT-$42IKE`2MwRPx`m1EHe4Ao7$VX94T+?)C9l?kRaY0 zH*{Sy>KVC#8_AOET#lo?udnClngOq}MGIX`Xc)A^jB6h}SVz&2d~y3<_o6t%wg@Dl ziuy`&vC<0?7uo63OO)BFq}QV%<~3Bpyx8AfzZ%z7a(9oG{3d-Ut7~FTAF>48hQ)4A*JZL1JZw5f*@cDg)NtQF} zbJ!&aBp5D*eRq_T;LG|c6n5O~8M9;c2l)52?u)}@viUD|o_}2yXZgFOUoxMzseP>1 zUzSTvzkJXLK;YI>{N6zMMf`c1`e+Wle$4pxEbuWneVsn%iN{b z0g0ACJ|YRFxy^PeM)D_U5F^lJ=L$$9H+e23aWzSaFYie2=y`!~bSZOv^Jru>PfHf!@0XSD0C zEal+Fx1Z`=t|_&gvkPGm!0T#`F!OHw$Q=x31Godg0ppQ)!Mc6?qImLMcYKi4^s9I# zSTFK=0~3^RHJ1-?4YZvB+&~>4A9~AgJbSG5)|VA~Y!ZrU?QV0f*EZCihK!^}NBL6*nv~fFOh$$x^k*Bl^M#*I!T^#+KIN?p{rtX4h;`zOdg|tE z>JW&92HG_Rqld(1X5v8|CZnY+^2o`#^X>VUnh@{by$`{d%!MW{)gz|K2=+H}@{LEX z-@P=x;DRPZE+eKt0r8X>E`jU2o({4yIdZ$X!@JF!DNM(l4^;O+*Ory;P zS`heBVrhnht4E8|`xEOt=YJxm$t^8x=gzx}w3`NGjdu06oxS@O8_^0cXN5fD@^yBZ znkq20R-5OwTJXNcPC?t`Z#R3|zkwu}LgG!&6*z@7Jy#SnJTi&&E7WpydB^kc7fDoH zsko)Pm>T;jH}PY;>b~Afwve#|@%+>b?eZ0t2kgy%OlT~vSdsV?x6)k6wXfv?X=&D1 zf;^mdT`{>Bl-7~4rc;?i*H{$VsF?dwOeMOwO1HRv)`x26#(D-vOem{oO-+1ELxowI*(z;auY@%o^DULV@Elz==|SG+wvbED*{J&4qJyffR4t{D zaN@5x@PC0r_7D00#A$wmV|G;cd`d_EMu(8!`LBbH))uoKF}UkoZ{cSyDN6J#2sPIu z4;OD07^Lq=4V{o>gvqiY|OlLoeHFM-!1~hIyEErwzB9Ip0tAt!Uvvabl?YF zQxcj+e~}Tq06~9&c0)<6l1bc`zTRd}WPOnR=@IJ0XW@O4h%*i%$8S*F?p$;1PKb=W zT~8VR6^*#V@NTBC+?<)t%~ATfUaWVHUsb({%}wkEdPn)ucd^-v$=uWO&iQ|#FFe(1FkXaR7m z0C|ZK9q)6t;N0&_#F->kL^A*%&{f^*Ox!ruG3YEV*3NOs-u=k+YE>;kp&M&cWLmK+ zW)=eq;L1f&h%a)WA+&_UA#J#`r7LZ#(d_sttGm{1h)NA$cZy$Gy*(7#{%;DM@~30H z+_Oz5Nn1FF=C9+O!|Qpj!NGH9Jb78pjB=ebN}3ds^@g&OizZfNqiQ=$NMu1Aw434f zJp(P{-f`xv+0sF+vH$`y0Hx(W9^o&^_=P}jm3$W?0&a6CHdS*&VGBs?nQeMh(n7rD z6qI2MQPpT*p6?(%jtc22_=BR>-e=9`gi=o)o=X|^$Ryf}OuUszH7twHv(1s)^4?Z5 z^FOrN0gyu89CUbiG4yBYoNVNuHjvL^rPBq_&z{H9bL=nasgiV7Tq>V!=efFB3yDwN z(@E^+ez#V~JB#tyFIkfB^Yz&_edu;gz3QMG$HJpae~y*CAw#bk0@nT_ObZjqL=+k zoo7XJse5G?!mB9m=7n-#?*({HGOmChJD_*al;F4UGjLFnL}Vr4CH|Z+e4(f4hdb}m zWB0!%B$yNm<=Qz<3YplMYM22l!rM2-+-!nGAFcMg3;i31Ru(ee4rj(_1JkgtZ5 z>Fr7-C-k=5?1kwfP+qDj$@yV-Mwxx+?lETwj4h*jloxk<^7;}qXrg6+m*qDI&F0n}sD5yA>cEKglDG~GZV>x*S1&Nx+Ih9WT|v zp6l2-#dK1evT5IN+M>HOJ z7^$WMq$4dCX}xBf8SLx%S4*3KImdp-iOd%i*J3+Ws&SUkN_t|K@%X{XZQbqN zq1oX-5K^(RUA>XD1IUSCtF?^dwlf!5tII%+}cbe4KFNOc|^O0O|7C+J>;|`Fu{Ckv6vFF%ukw2!-Rk-qWJuk z(=Su2=`)uOO%q9cO}eL=oDEr#HcqWmL1Jr>ISPe?Ly?BhwfUOC#T9Jl$BRO%jA>$c zf!E0H>jcLisEl3?d4Nk5B#V6tHR0PGpW_vFPY2cNp!s>&g`vl|Gj(lc{4Yugm0$q! z@s5uP>KeC{j37X~L42;PF_3XE<6%FL(3mUewm4RW(sQ#wetu?6Uze*r5gADTCs?hv z=I5J+8PhjD%iir5tp9|__brj!e(|3~7Aq2#d;duk5wtQP;4Kk7D`;ckbc*OH-qS>| zV2UW#A|riWz{C_0NeeQTEwhP?6ktS_nsakAX)dfbnJgsC%&y*Zq_;0f3gr)iT`~Z2 zU`M$=d(m&=kcOQUnuqG^W}&>J4O$t3W{H^XK@M-iqkM_=Amb)<3~2HH>fYw4Gc@vCS*&oecYLWhhKRO$nJ^ zk{TV$s|Vs16wO3ow}r8+Rz}_jc~Stj&%$=F$rKr4Jo7eYn*%XxCf6|j(3MI`x@UF} z1Erq@=tKM)mHs31Y^RkdGngRONXBpBQT-2EVZ;V1HRiiXBLZK>LvC*OTQgvn-mV$& zLw+}IX%JNeWFBw%nAF%#{~ zxE(E{cwk@wAUZKpU@v*1-xOp~AU_ekvK=&&p8+7HI6Q=$XX?Bx>jU@u*FU65H`1sp z$dn`pn&-pqZ(d=zzC9y4{jCu>%!gEiN|>CIg1Lba4WPx;*B(W4gIyYYlkHvICO`H# zAe~mSbg)mH`qm?H@>`D^@btGHG!#!k={NYphQEI6F~x*^>v6vPXPv}0fJU47fI9V- zdABswA^wG_PYQwf3+a~5n5qpF(alx9uGtaspT+V+>lcRFr6t1!s|}J zV(MjKaUPeKcf*AM_R%8zx0e${|KJ!NE32;W2m#R=yx+=s%rUveUZq|IbP0l+XkK8G}75 literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style5/controls.png b/web/img/globals/colorbox/style5/controls.png new file mode 100755 index 0000000000000000000000000000000000000000..65cfd1dc95c5ee4c6c3d0848b1dcfc0ec69f6aea GIT binary patch literal 2033 zcmVY16Dl9G~IT3Xc9)Z^ph#l^+1udkq>pj%s8x3{-FJw3a- zyTZc4tgNhJVPUGOs=2wjTUuIPU0s)#m&C-xo0^)sxw(yvjeLB3^78U4D=RH6Ei5c7 zO-)TME-o-IFwM=)IXOA(>+9v^gnk!Dk@A&OhZFMI5;>mGBP$cHrd(PIyySo z*x1a>%u7p4z`(#lLPF^0=SN3JF)=YpN=iXNLGSMFH#av(NJvFRMNv^v@$vCHJ3Bl) zJTo&hNl8iR>FGvBMo>^t>+0%WUS93(?OFDTAPEK`obwEHsK0ZFDr>E}i?Y_RgTwGjIQc{VDiO$Z>?Ck7QQ&aZ#_VDoVc6N4O zUti|s<#>2_pP!#*wE#@J%|ZXjkIv8d15RfRBQxe$bh z9TUw{N+{bY1z&)8>k2t;d{xYxg76&eJ5GSmab7cm2*kJn zLPONkBzslPWkA+%7~Rthzj+ZdH;HAk-Pq6JDgZ%}1qZgnS8` z|A!BGUTY0vuJ8~Q8k9-cggFrxrb5iS-G06qcV|ScTS@l&{bZ%90njJ((hDH>go-Ov zZbB$mN3X(#_fF&Gsyr1MX$KPpfH2>rC?r$(w4Y$dt3?|Rbk9;C`p(jcaF$k70KON_ zQXS~iuoPW9OL=;PXDK^qs;!en!tGwM8Dm32zK3Hnob~aBRzMgVd)VIwFSb@T|pRnFQtL^ zQVJ_)!)j6(b&PC(dhopzE0a4<>r<@qU z`!EtA#BilIes-lde&oVm?AXss0zni9@U)s;c2)_BE*-ve3qd+Xkx|kwB9dCkgI$6` z2)acBb&UMSYiGCKj*SBn@)q-Z5n&E~kT-AMBO%kChC#@X|0tXb=fe3-(?1oCZXuoB zLi}_KX?F|Z?iRyJn&Er?*=Fkav37KPz4^Q2i%?hdj$hRY5$X6y8bhS1yyTxuueKWC z5CSWPU;%-YXn=qr((#(QE2FU06#>QwQ5V+BI|UkI(*RRqvv@oZ-B#&@=C_U&BCHmt zC#jJURR)BX9qafF9iN(TLwU10A{?j~CXS78W{8Uu@HZ?#PAyEpFp1S*%EGlpZ`N&L zTO`O5246{p*$9hz`Xxjbs;;d1D5fGugh*6-y27O=ZrgP)q5m%J z%*b8(_kO793Uc&AR^jAtn*OA38fGEGyOcr5Fk7$*{qtd@niJ25{6h8_pFvpWJ0e#STIxNJ)pDn{H0&eE#G130bNngTwLQ z{r-aw>G;0=r|=|0AtWIQNk~Exl8}TXBq0e&sC~j#A{E;EQlD^{NCv_KN?|hzt4p`= zmiMYgEZpwcxm$P_8++%CQ5bCMy}oe)uer?ORv)df%L>+~SMRSwxVo4v42N~krH0-D zsqp7BEemF3oQ#jZH?};MT1`)gSQr3NRVET5olgiEnaToD3Vmr;&eR^luC95&D|F|1 zySmIE&VwgJDV!&k+duls&IXn^h1S?O8(9D%7J?Dx?3|Gb?cRJt`cH*#(!#mIWIQCf P00000NkvXXu0mjf=5OWV literal 0 HcmV?d00001 diff --git a/web/img/globals/colorbox/style5/loading.gif b/web/img/globals/colorbox/style5/loading.gif new file mode 100755 index 0000000000000000000000000000000000000000..b4695d811d4f84ed693eff7b5e6b4d61a3e8c48d GIT binary patch literal 9427 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 literal 0 HcmV?d00001 diff --git a/web/img/globals/tiles/80percentwhite.png b/web/img/globals/tiles/80percentwhite.png new file mode 100755 index 0000000000000000000000000000000000000000..f7a6ea8186472ec5f883942a1aa841819c508577 GIT binary patch literal 2836 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 m1qB5K1qH7L2LJ&7{{sL#a|9VKxq&$V0000?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx&hU`_H=O!skoI?^Wnik<2FYg2BwbA#0ks|3{qeCSU46qzIPZdOlF2gTe~DWM4f^|^4o literal 0 HcmV?d00001 diff --git a/web/img/globals/tiles/90percentF0F0F0.png b/web/img/globals/tiles/90percentF0F0F0.png new file mode 100755 index 0000000000000000000000000000000000000000..e05f1199186185d1f540846e74f57a04984b1ad0 GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9GG!XV7ZFl&wkP>?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx&hU`_H=O!skoK&=flIp#%+!~3``xJi4&L`7^J@Nv2ZMKeD5$^n9K}u{w!=KzV1x} PdYi%1)z4*}Q$iB}bkK2` literal 0 HcmV?d00001 diff --git a/web/img/globals/tiles/90percentblack.png b/web/img/globals/tiles/90percentblack.png new file mode 100755 index 0000000000000000000000000000000000000000..da4e1ebdbde2fd5b41a01d47e189e783ec979800 GIT binary patch literal 372 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9GG!XV7ZFl&wkP>?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx&hU`@^ogTe~DWM4fD*9`c literal 0 HcmV?d00001 diff --git a/web/img/globals/tiles/90percentwhite.png b/web/img/globals/tiles/90percentwhite.png new file mode 100755 index 0000000000000000000000000000000000000000..f521c12d3baca6b10cdbb0c87d3beb8979ca6add GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9GG!XV7ZFl&wkP>?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx&hU`_H=O!skoK&=l}oz#%+!~3``xJi4&L`7^J@Nv2ZMKeD5$^n9K~PELn^-+RhpQ Pz0Kh1>gTe~DWM4f(GPJ_ literal 0 HcmV?d00001 diff --git a/web/index.php b/web/index.php new file mode 100755 index 0000000..39ae5be --- /dev/null +++ b/web/index.php @@ -0,0 +1,17 @@ +get($key, $defaultValue, $replacements); +} +function view($path,$objs=array()) { + \nano\core\view\View::load($path, $objs); +} +$log = \nano\core\log\Log::getInstance(); +$routing = \nano\core\routing\Routing::getInstance(); \ No newline at end of file diff --git a/web/js/globals/admin.js b/web/js/globals/admin.js new file mode 100644 index 0000000..505b60c --- /dev/null +++ b/web/js/globals/admin.js @@ -0,0 +1,17 @@ +//************************************************************************ +// Site JS Core - Extend with Ajax Class +//************************************************************************ +NJSCORE.extend('Admin',function(){ + this.setValueAndSubmitForm = function(elmId,elmValue,formId){ + $('#'+elmId).attr('value',elmValue); + $('#'+formId).submit(); + } + this.applySort = function(fieldName,formId){ + $('#orderby-field').attr('value',fieldName); + if($('#orderby-order').attr('value')=='asc'){ + this.setValueAndSubmitForm('orderby-order','desc',formId); + } else { + this.setValueAndSubmitForm('orderby-order','asc',formId); + } + } +}); \ No newline at end of file diff --git a/web/js/globals/core.js b/web/js/globals/core.js new file mode 100644 index 0000000..3c0f7b6 --- /dev/null +++ b/web/js/globals/core.js @@ -0,0 +1,164 @@ +//************************************************************************ +// Author: Christopher Beck & Alex Cipriani +// Copyright: Wired IDS Ltd 2010 +// Requirements: +// - '/css/default.css' +// - '/css/colorbox-default.css' +// - '/js/jquery.min.js' +// - '/js/jquery-ui.min.js' +// - '/js/jquery.colorbox-min.js' +//************************************************************************ +// Site JS Core +//************************************************************************ +var NJSCORE = {}; +//************************************************************************ +// Site JS Core - Add Class Extension Function +//************************************************************************ +NJSCORE['extend'] = function(namespace,obj){ + if(namespace!='extend'){ + var namespaces = namespace.split('.'); + var objPointer = NJSCORE; + if(namespaces.length>1){ + for(var i=0;i<(namespaces.length-1);i++){ + var currentNamespace = namespaces[i]; + if(!objPointer[currentNamespace]){ + objPointer[currentNamespace] = {}; + } + objPointer = objPointer[currentNamespace]; + } + namespace = namespaces[namespaces.length-1]; + } + objPointer[namespace] = new obj; + } +} +//************************************************************************ +// Site JS Core - Extend with Logging Bar Class +//************************************************************************ +NJSCORE.extend('LoggingBar',function(){ + var isLoggingBarShown = true; + var isLoggingBarDetailsShown = false; + this.closeLoggingBar = function(){ + $('#core-logging-bar').hide(); + } + this.toggleLoggingDetails = function(){ + if(isLoggingBarDetailsShown){ + $('#core-logging-bar-show-details-span').html('Show Details'); + $('#core-logging-bar-details').hide(); + isLoggingBarDetailsShown = false; + } else { + $('#core-logging-bar-show-details-span').html('Hide Details'); + $('#core-logging-bar-details').show(); + isLoggingBarDetailsShown = true; + } + } + this.popUpDetails = function(){ + NJSCORE.LightBox.htmlLoad('

    '); + } +}); +//************************************************************************ +// Site JS Core - Extend with Light Box Class +//************************************************************************ +NJSCORE.extend('LightBox',function(){ + this.load = function(config){ + $.fn.colorbox(config); + } + this.htmlLoad = function(body,title,onContentLoad,onClose){ + if(title){ + this.load({title:title,html:body,onComplete:onContentLoad,onClose:onClose,opacity:0.75}); + } else { + this.load({html:body,onComplete:onContentLoad,onClose:onClose,opacity:0.75}); + } + } + this.ajaxLoad = function(url,onContentLoad,onClose){ + this.load({href:url,onComplete:onContentLoad,onClose:onClose,opacity:0.75}); + } + this.resize = function(){ + $.fn.colorbox.resize(); + } + this.postAjaxLoad = function(url,formId,onSuccess){ + $.ajax({ + url: url, + cache: false, + global: false, + type: 'POST', + data: $('#'+formId).serialize(), + dataType: 'html', + beforeSend: function(){ + NJSCORE.LightBox.htmlLoad('
     
    '); + }, + success: function(rtn){ + NJSCORE.LightBox.htmlLoad(rtn); + if(onSuccess){ + onSuccess(); + } + }, + error: function(){ + NJSCORE.LightBox.htmlLoad('Oooops, something has gone a little wrong here...'); + } + }); + } +}); +//************************************************************************ +// Site JS Core - Extend with Ajax Class +//************************************************************************ +NJSCORE.extend('Ajax',function(){ + this.getRequest = function(id,url,onSuccess){ + $.ajax({ + url: url, + cache: false, + global: false, + type: 'GET', + dataType: 'html', + success: function(rtn){ + $('#'+id).html(rtn); + if(onSuccess){ + onSuccess(); + } + }, + error: function(){ + $('#'+id).html('Oooops, something has gone a little wrong here...'); + } + }); + } + this.postRequest = function(id,url,data,onSuccess){ + $.ajax({ + url: url, + cache: false, + global: false, + type: 'POST', + data: data, + dataType: 'html', + success: function(rtn){ + $('#'+id).html(rtn); + if(onSuccess){ + onSuccess(); + } + }, + error: function(){ + $('#'+id).html('Oooops, something has gone a little wrong here...'); + } + }); + } + this.formPostRequest = function(id,url,formId,onSuccess){ + $.ajax({ + url: url, + cache: false, + global: false, + type: 'POST', + data: $('#'+formId).serialize(), + dataType: 'html', + beforeSend: function(){ + $('#'+id).html('
     
    '); + }, + success: function(rtn){ + $('#'+id).html(rtn); + if(onSuccess){ + onSuccess(); + } + }, + error: function(){ + $('#'+id).html('Oooops, something has gone a little wrong here...'); + } + }); + } +}); \ No newline at end of file diff --git a/web/js/globals/jquery-ui.min.js b/web/js/globals/jquery-ui.min.js new file mode 100755 index 0000000..827b5f0 --- /dev/null +++ b/web/js/globals/jquery-ui.min.js @@ -0,0 +1,778 @@ +/*! + * jQuery UI 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.5",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106, +NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this, +"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position"); +if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"));if(!isNaN(b)&&b!=0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind("mousedown.ui-disableSelection selectstart.ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f, +"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c.style(this,h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c.style(this, +h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}}); +c(function(){var a=document.createElement("div"),b=document.body;c.extend(a.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.appendChild(a).offsetHeight===100;b.removeChild(a).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +;/* + * jQuery UI Position 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Position + */ +(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.scrollTo&&d.document){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j= +{top:b.of.pageY,left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/ +2;if(b.at[1]==="bottom")j.top+=k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+parseInt(c.curCSS(this,"marginRight",true))||0,w=m+q+parseInt(c.curCSS(this,"marginBottom",true))||0,i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]=== +"center")i.top-=m/2;i.left=parseInt(i.left);i.top=parseInt(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft(); +b.left=d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0]; +b.left+=a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d= +c(b),g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); +;/* + * jQuery UI Draggable 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Draggables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== +"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= +this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;return true},_mouseStart:function(a){var b=this.options;this.helper=this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top- +this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions(); +d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);return true},_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis|| +this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b=false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if(!this.element[0]||!this.element[0].parentNode)return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element, +b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle||!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this== +a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone():this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]|| +0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0], +this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top- +(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment== +"parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&& +a.containment.constructor!=Array){var b=d(a.containment)[0];if(b){a=d(a.containment).offset();var c=d(b).css("overflow")!="hidden";this.containment=[a.left+(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0)-this.margins.left,a.top+(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0)-this.margins.top,a.left+(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"), +10)||0)-this.helperProportions.width-this.margins.left,a.top+(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0], +this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft(): +f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,g=a.pageY;if(this.originalPosition){if(this.containment){if(a.pageX-this.offset.click.leftthis.containment[2])e=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.topthis.containment[3])?g:!(g-this.offset.click.topthis.containment[2])?e:!(e-this.offset.click.left').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")})},stop:function(){d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options; +if(a.css("opacity"))b._opacity=a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable");if(a.scrollParent[0]!=document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!= +"HTML"){if(!c.axis||c.axis!="x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY=0;h--){var i=c.snapElements[h].left,k=i+c.snapElements[h].width,j=c.snapElements[h].top,l=j+c.snapElements[h].height;if(i-e=j&&f<=l||h>=j&&h<=l||fl)&&(e>= +i&&e<=k||g>=i&&g<=k||ek);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(), +top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle= +this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne", +nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor== +String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),k=0;k=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,k);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection(); +this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){e(this).removeClass("ui-resizable-autohide");b._handles.show()},function(){if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()}; +if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(), +d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"});this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset= +this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio: +this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis];if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize", +b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height; +f={width:c.size.width-(f?0:c.sizeDiff.width),height:c.size.height-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f,{top:g,left:d}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop", +b);this._helper&&this.helper.remove();return false},_updateCache:function(b){this.offset=this.helper.offset();if(l(b.left))this.position.left=b.left;if(l(b.top))this.position.top=b.top;if(l(b.height))this.size.height=b.height;if(l(b.width))this.size.width=b.width},_updateRatio:function(b){var a=this.position,c=this.size,d=this.axis;if(b.height)b.width=c.height*this.aspectRatio;else if(b.width)b.height=c.width/this.aspectRatio;if(d=="sw"){b.left=a.left+(c.width-b.width);b.top=null}if(d=="nw"){b.top= +a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this.options,c=this.axis,d=l(b.width)&&a.maxWidth&&a.maxWidthb.width,h=l(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height, +k=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&k)b.left=i-a.minWidth;if(d&&k)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+ +a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this, +arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]);b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});e.extend(e.ui.resizable, +{version:"1.8.5"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(),10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize, +function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top-f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var k=e(this),q=e(this).data("resizable-alsoresize"),p={},r=j&&j.length?j:k.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(r,function(n,o){if((n= +(q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(k.css("position"))){c._revertToRelativePosition=true;k.css({position:"absolute",top:"auto",left:"auto"})}k.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType?e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})};if(b._revertToRelativePosition){b._revertToRelativePosition= +false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a=e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-g};g=parseInt(a.element.css("left"),10)+(a.position.left- +a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing,step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize", +b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement=e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d=e(a),f=[];e(["Top", +"Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset;var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options,d=a.containerOffset, +f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left:a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?d.top:0}a.offset.left= +a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top-d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(d+ +a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition,f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&&/static/.test(f.css("position"))&& +e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25,display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable");b.ghost&&b.ghost.css({position:"relative", +height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b=e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width= +d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,10)||0},l=function(b){return!isNaN(parseInt(b,10))}})(jQuery); +;/* + * jQuery UI Selectable 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(e){e.widget("ui.selectable",e.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var c=this;this.element.addClass("ui-selectable");this.dragged=false;var f;this.refresh=function(){f=e(c.options.filter,c.element[0]);f.each(function(){var d=e(this),b=d.offset();e.data(this,"selectable-item",{element:this,$element:d,left:b.left,top:b.top,right:b.left+d.outerWidth(),bottom:b.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"), +selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=f.addClass("ui-selectee");this._mouseInit();this.helper=e("
    ")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(c){var f=this;this.opos=[c.pageX, +c.pageY];if(!this.options.disabled){var d=this.options;this.selectees=e(d.filter,this.element[0]);this._trigger("start",c);e(d.appendTo).append(this.helper);this.helper.css({left:c.clientX,top:c.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var b=e.data(this,"selectable-item");b.startselected=true;if(!c.metaKey){b.$element.removeClass("ui-selected");b.selected=false;b.$element.addClass("ui-unselecting");b.unselecting=true;f._trigger("unselecting", +c,{unselecting:b.element})}});e(c.target).parents().andSelf().each(function(){var b=e.data(this,"selectable-item");if(b){var g=!c.metaKey||!b.$element.hasClass("ui-selected");b.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");b.unselecting=!g;b.selecting=g;(b.selected=g)?f._trigger("selecting",c,{selecting:b.element}):f._trigger("unselecting",c,{unselecting:b.element});return false}})}},_mouseDrag:function(c){var f=this;this.dragged=true;if(!this.options.disabled){var d= +this.options,b=this.opos[0],g=this.opos[1],h=c.pageX,i=c.pageY;if(b>h){var j=h;h=b;b=j}if(g>i){j=i;i=g;g=j}this.helper.css({left:b,top:g,width:h-b,height:i-g});this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!(!a||a.element==f.element[0])){var k=false;if(d.tolerance=="touch")k=!(a.left>h||a.righti||a.bottomb&&a.rightg&&a.bottom *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){this.containerCache={};this.element.addClass("ui-sortable"); +this.refresh();this.floating=this.items.length?/left|right/.test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a==="disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(this, +arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&&!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem= +c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset, +{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment(); +if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start", +a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a);return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute"); +if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0],e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a, +c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset();c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]== +document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp();this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var b=this.containers.length-1;b>=0;b--){this.containers[b]._trigger("deactivate", +null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem): +d(this.domPosition.parent).prepend(this.currentItem);return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});!c.length&&a.key&&c.push(a.key+"=");return c.join("&")},toArray:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute|| +"id")||"")});return c},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+jg&&b+la[this.floating?"width":"height"]?j:g0?"down":"up")}, +_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith();if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h=d.data(f[g],"sortable");if(h&&h!= +this&&!h.options.disabled)c.push([d.isFunction(h.options.items)?h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)});return d(b)},_removeCurrentsFromItems:function(){for(var a= +this.currentItem.find(":data(sortable-item)"),b=0;b=0;f--)for(var g=d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable"); +if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)?i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h= +0;b--){var c=this.items[b],e=this.options.toleranceElement?d(this.options.toleranceElement,c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b=this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top=e.top;this.containers[b].containerCache.width= +this.containers[b].element.outerWidth();this.containers[b].containerCache.height=this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f=d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!e)f.style.visibility="hidden";return f}, +update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b= +null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0],this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length===1){this.containers[c]._trigger("over",a,this._uiHash(this)); +this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b=1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h-f)this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.topthis.containment[3])? +g:!(g-this.offset.click.topthis.containment[2])?f:!(f-this.offset.click.left=0;e--)if(d.ui.contains(this.containers[e].element[0],this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive", +g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update",g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this,this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out",g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over= +0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop",a,this._uiHash());for(e=0;e li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,b=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"); +a.headers=a.element.find(b.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){b.disabled||c(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){b.disabled||c(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){b.disabled||c(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){b.disabled||c(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom"); +if(b.navigation){var d=a.element.find("a").filter(b.navigationFilter).eq(0);if(d.length){var f=d.closest(".ui-accordion-header");a.active=f.length?f:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||b.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion",function(g){return a._keydown(g)}).next().attr("role", +"tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);c.browser.safari||a.headers.find("a").attr("tabIndex",-1);b.event&&a.headers.bind(b.event.split(" ").join(".accordion ")+".accordion",function(g){a._clickHandler.call(a,g,this);g.preventDefault()})},_createIcons:function(){var a=this.options;if(a.icons){c("").addClass("ui-icon "+a.icons.header).prependTo(this.headers); +this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabIndex"); +this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(a.autoHeight||a.fillHeight)b.css("height","");return c.Widget.prototype.destroy.call(this)},_setOption:function(a,b){c.Widget.prototype._setOption.apply(this,arguments);a=="active"&&this.activate(b);if(a=="icons"){this._destroyIcons(); +b&&this._createIcons()}if(a=="disabled")this.headers.add(this.headers.next())[b?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!(this.options.disabled||a.altKey||a.ctrlKey)){var b=c.ui.keyCode,d=this.headers.length,f=this.headers.index(a.target),g=false;switch(a.keyCode){case b.RIGHT:case b.DOWN:g=this.headers[(f+1)%d];break;case b.LEFT:case b.UP:g=this.headers[(f-1+d)%d];break;case b.SPACE:case b.ENTER:this._clickHandler({target:a.target},a.target); +a.preventDefault()}if(g){c(a.target).attr("tabIndex",-1);c(g).attr("tabIndex",0);g.focus();return false}return true}},resize:function(){var a=this.options,b;if(a.fillSpace){if(c.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}b=this.element.parent().height();c.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){b-=c(this).outerHeight(true)});this.headers.next().each(function(){c(this).height(Math.max(0,b-c(this).innerHeight()+ +c(this).height()))}).css("overflow","auto")}else if(a.autoHeight){b=0;this.headers.next().each(function(){b=Math.max(b,c(this).height("").height())}).height(b)}return this},activate:function(a){this.options.active=a;a=this._findActive(a)[0];this._clickHandler({target:a},a);return this},_findActive:function(a){return a?typeof a==="number"?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):a===false?c([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,b){var d=this.options; +if(!d.disabled)if(a.target){a=c(a.currentTarget||b);b=a[0]===this.active[0];d.active=d.collapsible&&b?false:this.headers.index(a);if(!(this.running||!d.collapsible&&b)){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);if(!b){a.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected); +a.next().addClass("ui-accordion-content-active")}h=a.next();f=this.active.next();g={options:d,newHeader:b&&d.collapsible?c([]):a,oldHeader:this.active,newContent:b&&d.collapsible?c([]):h,oldContent:f};d=this.headers.index(this.active[0])>this.headers.index(a[0]);this.active=b?c([]):a;this._toggle(h,f,g,b,d)}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header); +this.active.next().addClass("ui-accordion-content-active");var f=this.active.next(),g={options:d,newHeader:c([]),oldHeader:d.active,newContent:c([]),oldContent:f},h=this.active=c([]);this._toggle(h,f,g)}},_toggle:function(a,b,d,f,g){var h=this,e=h.options;h.toShow=a;h.toHide=b;h.data=d;var j=function(){if(h)return h._completed.apply(h,arguments)};h._trigger("changestart",null,h.data);h.running=b.size()===0?a.size():b.size();if(e.animated){d={};d=e.collapsible&&f?{toShow:c([]),toHide:b,complete:j, +down:g,autoHeight:e.autoHeight||e.fillSpace}:{toShow:a,toHide:b,complete:j,down:g,autoHeight:e.autoHeight||e.fillSpace};if(!e.proxied)e.proxied=e.animated;if(!e.proxiedDuration)e.proxiedDuration=e.duration;e.animated=c.isFunction(e.proxied)?e.proxied(d):e.proxied;e.duration=c.isFunction(e.proxiedDuration)?e.proxiedDuration(d):e.proxiedDuration;f=c.ui.accordion.animations;var i=e.duration,k=e.animated;if(k&&!f[k]&&!c.easing[k])k="slide";f[k]||(f[k]=function(l){this.slide(l,{easing:k,duration:i||700})}); +f[k](d)}else{if(e.collapsible&&f)a.toggle();else{b.hide();a.show()}j(true)}b.prev().attr({"aria-expanded":"false",tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");this._trigger("change",null,this.data)}}});c.extend(c.ui.accordion,{version:"1.8.5",animations:{slide:function(a, +b){a=c.extend({easing:"swing",duration:300},a,b);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),f=0,g={},h={},e;b=a.toShow;e=b[0].style.width;b.width(parseInt(b.parent().width(),10)-parseInt(b.css("paddingLeft"),10)-parseInt(b.css("paddingRight"),10)-(parseInt(b.css("borderLeftWidth"),10)||0)-(parseInt(b.css("borderRightWidth"),10)||0));c.each(["height","paddingTop","paddingBottom"],function(j,i){h[i]="hide";j=(""+c.css(a.toShow[0],i)).match(/^([\d+-.]+)(.*)$/);g[i]={value:j[1], +unit:j[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(h,{step:function(j,i){if(i.prop=="height")f=i.end-i.start===0?0:(i.now-i.start)/(i.end-i.start);a.toShow[0].style[i.prop]=f*g[i.prop].value+g[i.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:e,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide",paddingTop:"hide", +paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1E3:200})}}})})(jQuery); +;/* + * jQuery UI Autocomplete 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + */ +(function(e){e.widget("ui.autocomplete",{options:{appendTo:"body",delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},_create:function(){var a=this,b=this.element[0].ownerDocument;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!a.options.disabled){var d=e.ui.keyCode;switch(c.keyCode){case d.PAGE_UP:a._move("previousPage", +c);break;case d.PAGE_DOWN:a._move("nextPage",c);break;case d.UP:a._move("previous",c);c.preventDefault();break;case d.DOWN:a._move("next",c);c.preventDefault();break;case d.ENTER:case d.NUMPAD_ENTER:a.menu.element.is(":visible")&&c.preventDefault();case d.TAB:if(!a.menu.active)return;a.menu.select(c);break;case d.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay); +break}}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};this.menu=e("
      ").addClass("ui-autocomplete").appendTo(e(this.options.appendTo||"body",b)[0]).mousedown(function(c){var d=a.menu.element[0]; +c.target===d&&setTimeout(function(){e(document).one("mousedown",function(f){f.target!==a.element[0]&&f.target!==d&&!e.ui.contains(d,f.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,d){d=d.item.data("item.autocomplete");false!==a._trigger("focus",null,{item:d})&&/^key/.test(c.originalEvent.type)&&a.element.val(d.value)},selected:function(c,d){d=d.item.data("item.autocomplete");var f=a.previous;if(a.element[0]!==b.activeElement){a.element.focus(); +a.previous=f}if(false!==a._trigger("select",c,{item:d})){a.term=d.value;a.element.val(d.value)}a.close(c);a.selectedItem=d},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");e.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"); +this.menu.element.remove();e.Widget.prototype.destroy.call(this)},_setOption:function(a,b){e.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(e(b||"body",this.element[0].ownerDocument)[0])},_initSource:function(){var a=this,b,c;if(e.isArray(this.options.source)){b=this.options.source;this.source=function(d,f){f(e.ui.autocomplete.filter(b,d.term))}}else if(typeof this.options.source==="string"){c=this.options.source;this.source= +function(d,f){a.xhr&&a.xhr.abort();a.xhr=e.getJSON(c,d,function(g,i,h){h===a.xhr&&f(g);a.xhr=null})}}else this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length").data("item.autocomplete",b).append(e("
      ").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});e.extend(e.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")}, +filter:function(a,b){var c=new RegExp(e.ui.autocomplete.escapeRegex(b),"i");return e.grep(a,function(d){return c.test(d.label||d.value||d)})}})})(jQuery); +(function(e){e.widget("ui.menu",{_create:function(){var a=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(b){if(e(b.target).closest(".ui-menu-item a").length){b.preventDefault();a.select(b)}});this.refresh()},refresh:function(){var a=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", +-1).mouseenter(function(b){a.activate(b,e(this).parent())}).mouseleave(function(){a.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.attr("scrollTop"),f=this.element.height();if(c<0)this.element.attr("scrollTop",d+c);else c>=f&&this.element.attr("scrollTop",d+c-f+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",a,{item:b})}, +deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(this.active){a=this.active[a+"All"](".ui-menu-item").eq(0); +a.length?this.activate(c,a):this.activate(c,this.element.children(b))}else this.activate(c,this.element.children(b))},nextPage:function(a){if(this.hasScroll())if(!this.active||this.last())this.activate(a,this.element.children(":first"));else{var b=this.active.offset().top,c=this.element.height(),d=this.element.children("li").filter(function(){var f=e(this).offset().top-b-c+e(this).height();return f<10&&f>-10});d.length||(d=this.element.children(":last"));this.activate(a,d)}else this.activate(a,this.element.children(!this.active|| +this.last()?":first":":last"))},previousPage:function(a){if(this.hasScroll())if(!this.active||this.first())this.activate(a,this.element.children(":last"));else{var b=this.active.offset().top,c=this.element.height();result=this.element.children("li").filter(function(){var d=e(this).offset().top-b+c-e(this).height();return d<10&&d>-10});result.length||(result=this.element.children(":first"));this.activate(a,result)}else this.activate(a,this.element.children(!this.active||this.first()?":last":":first"))}, +hasScroll:function(){return this.element.height()").addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary;if(d.primary||d.secondary){b.addClass("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary"));d.primary&&b.prepend("");d.secondary&&b.append("");if(!this.options.text){b.addClass(e?"ui-button-icons-only":"ui-button-icon-only").removeClass("ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary"); +this.hasTitle||b.attr("title",c)}}else b.addClass("ui-button-text-only")}}});a.widget("ui.buttonset",{_create:function(){this.element.addClass("ui-buttonset");this._init()},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(":button, :submit, :reset, :checkbox, :radio, a, :data(button)").filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":visible").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end().end()}, +destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");a.Widget.prototype.destroy.call(this)}})})(jQuery); +;/* + * jQuery UI Dialog 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + * jquery.ui.draggable.js + * jquery.ui.mouse.js + * jquery.ui.position.js + * jquery.ui.resizable.js + */ +(function(c,j){c.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:{my:"center",at:"center",of:window,collision:"fit",using:function(a){var b=c(this).css(a).offset().top;b<0&&c(this).css("top",a.top-b)}},resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title"); +if(typeof this.originalTitle!=="string")this.originalTitle="";this.options.title=this.options.title||this.originalTitle;var a=this,b=a.options,d=b.title||" ",f=c.ui.dialog.getTitleId(a.element),g=(a.uiDialog=c("
      ")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b.dialogClass).css({zIndex:b.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(i){if(b.closeOnEscape&&i.keyCode&&i.keyCode===c.ui.keyCode.ESCAPE){a.close(i);i.preventDefault()}}).attr({role:"dialog", +"aria-labelledby":f}).mousedown(function(i){a.moveToTop(false,i)});a.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g);var e=(a.uiDialogTitlebar=c("
      ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),h=c('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){h.addClass("ui-state-hover")},function(){h.removeClass("ui-state-hover")}).focus(function(){h.addClass("ui-state-focus")}).blur(function(){h.removeClass("ui-state-focus")}).click(function(i){a.close(i); +return false}).appendTo(e);(a.uiDialogTitlebarCloseText=c("")).addClass("ui-icon ui-icon-closethick").text(b.closeText).appendTo(h);c("").addClass("ui-dialog-title").attr("id",f).html(d).prependTo(e);if(c.isFunction(b.beforeclose)&&!c.isFunction(b.beforeClose))b.beforeClose=b.beforeclose;e.find("*").add(e).disableSelection();b.draggable&&c.fn.draggable&&a._makeDraggable();b.resizable&&c.fn.resizable&&a._makeResizable();a._createButtons(b.buttons);a._isOpen=false;c.fn.bgiframe&& +g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy();a.uiDialog.hide();a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");a.uiDialog.remove();a.originalTitle&&a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(a){var b=this,d;if(false!==b._trigger("beforeClose",a)){b.overlay&&b.overlay.destroy();b.uiDialog.unbind("keypress.ui-dialog"); +b._isOpen=false;if(b.options.hide)b.uiDialog.hide(b.options.hide,function(){b._trigger("close",a)});else{b.uiDialog.hide();b._trigger("close",a)}c.ui.dialog.overlay.resize();if(b.options.modal){d=0;c(".ui-dialog").each(function(){if(this!==b.uiDialog[0])d=Math.max(d,c(this).css("z-index"))});c.ui.dialog.maxZ=d}return b}},isOpen:function(){return this._isOpen},moveToTop:function(a,b){var d=this,f=d.options;if(f.modal&&!a||!f.stack&&!f.modal)return d._trigger("focus",b);if(f.zIndex>c.ui.dialog.maxZ)c.ui.dialog.maxZ= +f.zIndex;if(d.overlay){c.ui.dialog.maxZ+=1;d.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=c.ui.dialog.maxZ)}a={scrollTop:d.element.attr("scrollTop"),scrollLeft:d.element.attr("scrollLeft")};c.ui.dialog.maxZ+=1;d.uiDialog.css("z-index",c.ui.dialog.maxZ);d.element.attr(a);d._trigger("focus",b);return d},open:function(){if(!this._isOpen){var a=this,b=a.options,d=a.uiDialog;a.overlay=b.modal?new c.ui.dialog.overlay(a):null;d.next().length&&d.appendTo("body");a._size();a._position(b.position);d.show(b.show); +a.moveToTop(true);b.modal&&d.bind("keypress.ui-dialog",function(f){if(f.keyCode===c.ui.keyCode.TAB){var g=c(":tabbable",this),e=g.filter(":first");g=g.filter(":last");if(f.target===g[0]&&!f.shiftKey){e.focus(1);return false}else if(f.target===e[0]&&f.shiftKey){g.focus(1);return false}}});c(a.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus();a._isOpen=true;a._trigger("open");return a}},_createButtons:function(a){var b=this,d=false, +f=c("
      ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=c("
      ").addClass("ui-dialog-buttonset").appendTo(f);b.uiDialog.find(".ui-dialog-buttonpane").remove();typeof a==="object"&&a!==null&&c.each(a,function(){return!(d=true)});if(d){c.each(a,function(e,h){h=c.isFunction(h)?{click:h,text:e}:h;e=c("",h).unbind("click").click(function(){h.click.apply(b.element[0],arguments)}).appendTo(g);c.fn.button&&e.button()});f.appendTo(b.uiDialog)}},_makeDraggable:function(){function a(e){return{position:e.position, +offset:e.offset}}var b=this,d=b.options,f=c(document),g;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(e,h){g=d.height==="auto"?"auto":c(this).height();c(this).height(c(this).height()).addClass("ui-dialog-dragging");b._trigger("dragStart",e,a(h))},drag:function(e,h){b._trigger("drag",e,a(h))},stop:function(e,h){d.position=[h.position.left-f.scrollLeft(),h.position.top-f.scrollTop()];c(this).removeClass("ui-dialog-dragging").height(g); +b._trigger("dragStop",e,a(h));c.ui.dialog.overlay.resize()}})},_makeResizable:function(a){function b(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}a=a===j?this.options.resizable:a;var d=this,f=d.options,g=d.uiDialog.css("position");a=typeof a==="string"?a:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:d._minHeight(), +handles:a,start:function(e,h){c(this).addClass("ui-dialog-resizing");d._trigger("resizeStart",e,b(h))},resize:function(e,h){d._trigger("resize",e,b(h))},stop:function(e,h){c(this).removeClass("ui-dialog-resizing");f.height=c(this).height();f.width=c(this).width();d._trigger("resizeStop",e,b(h));c.ui.dialog.overlay.resize()}}).css("position",g).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight, +a.height)},_position:function(a){var b=[],d=[0,0],f;if(a){if(typeof a==="string"||typeof a==="object"&&"0"in a){b=a.split?a.split(" "):[a[0],a[1]];if(b.length===1)b[1]=b[0];c.each(["left","top"],function(g,e){if(+b[g]===b[g]){d[g]=b[g];b[g]=e}});a={my:b.join(" "),at:b.join(" "),offset:d.join(" ")}}a=c.extend({},c.ui.dialog.prototype.options.position,a)}else a=c.ui.dialog.prototype.options.position;(f=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(a); +f||this.uiDialog.hide()},_setOption:function(a,b){var d=this,f=d.uiDialog,g=f.is(":data(resizable)"),e=false;switch(a){case "beforeclose":a="beforeClose";break;case "buttons":d._createButtons(b);e=true;break;case "closeText":d.uiDialogTitlebarCloseText.text(""+b);break;case "dialogClass":f.removeClass(d.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b);break;case "disabled":b?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case "draggable":b? +d._makeDraggable():f.draggable("destroy");break;case "height":e=true;break;case "maxHeight":g&&f.resizable("option","maxHeight",b);e=true;break;case "maxWidth":g&&f.resizable("option","maxWidth",b);e=true;break;case "minHeight":g&&f.resizable("option","minHeight",b);e=true;break;case "minWidth":g&&f.resizable("option","minWidth",b);e=true;break;case "position":d._position(b);break;case "resizable":g&&!b&&f.resizable("destroy");g&&typeof b==="string"&&f.resizable("option","handles",b);!g&&b!==false&& +d._makeResizable(b);break;case "title":c(".ui-dialog-title",d.uiDialogTitlebar).html(""+(b||" "));break;case "width":e=true;break}c.Widget.prototype._setOption.apply(d,arguments);e&&d._size()},_size:function(){var a=this.options,b;this.element.css({width:"auto",minHeight:0,height:0});if(a.minWidth>a.width)a.width=a.minWidth;b=this.uiDialog.css({height:"auto",width:a.width}).height();this.element.css(a.height==="auto"?{minHeight:Math.max(a.minHeight-b,0),height:c.support.minHeight?"auto":Math.max(a.minHeight- +b,0)}:{minHeight:0,height:Math.max(a.height-b,0)}).show();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}});c.extend(c.ui.dialog,{version:"1.8.5",uuid:0,maxZ:0,getTitleId:function(a){a=a.attr("id");if(!a){this.uuid+=1;a=this.uuid}return"ui-dialog-title-"+a},overlay:function(a){this.$el=c.ui.dialog.overlay.create(a)}});c.extend(c.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","), +function(a){return a+".dialog-overlay"}).join(" "),create:function(a){if(this.instances.length===0){setTimeout(function(){c.ui.dialog.overlay.instances.length&&c(document).bind(c.ui.dialog.overlay.events,function(d){if(c(d.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});c.fn.bgiframe&&b.bgiframe();this.instances.push(b);return b},destroy:function(a){this.oldInstances.push(this.instances.splice(c.inArray(a,this.instances),1)[0]);this.instances.length===0&&c([document,window]).unbind(".dialog-overlay");a.remove();var b=0;c.each(this.instances,function(){b=Math.max(b,this.css("z-index"))});this.maxZ=b},height:function(){var a, +b;if(c.browser.msie&&c.browser.version<7){a=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);b=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return a");if(!b.values)b.values=[this._valueMin(),this._valueMin()];if(b.values.length&&b.values.length!==2)b.values=[b.values[0],b.values[0]]}else this.range=d("
      ");this.range.appendTo(this.element).addClass("ui-slider-range");if(b.range==="min"||b.range==="max")this.range.addClass("ui-slider-range-"+b.range);this.range.addClass("ui-widget-header")}d(".ui-slider-handle",this.element).length===0&&d("").appendTo(this.element).addClass("ui-slider-handle"); +if(b.values&&b.values.length)for(;d(".ui-slider-handle",this.element).length").appendTo(this.element).addClass("ui-slider-handle");this.handles=d(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(c){c.preventDefault()}).hover(function(){b.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(b.disabled)d(this).blur(); +else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(c){d(this).data("index.ui-slider-handle",c)});this.handles.keydown(function(c){var e=true,f=d(this).data("index.ui-slider-handle"),h,g,i;if(!a.options.disabled){switch(c.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:e= +false;if(!a._keySliding){a._keySliding=true;d(this).addClass("ui-state-active");h=a._start(c,f);if(h===false)return}break}i=a.options.step;h=a.options.values&&a.options.values.length?(g=a.values(f)):(g=a.value());switch(c.keyCode){case d.ui.keyCode.HOME:g=a._valueMin();break;case d.ui.keyCode.END:g=a._valueMax();break;case d.ui.keyCode.PAGE_UP:g=a._trimAlignValue(h+(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:g=a._trimAlignValue(h-(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(h=== +a._valueMax())return;g=a._trimAlignValue(h+i);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(h===a._valueMin())return;g=a._trimAlignValue(h-i);break}a._slide(c,f,g);return e}}).keyup(function(c){var e=d(this).data("index.ui-slider-handle");if(a._keySliding){a._keySliding=false;a._stop(c,e);a._change(c,e);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"); +this._mouseDestroy();return this},_mouseCapture:function(a){var b=this.options,c,e,f,h,g;if(b.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:a.pageX,y:a.pageY});e=this._valueMax()-this._valueMin()+1;h=this;this.handles.each(function(i){var j=Math.abs(c-h.values(i));if(e>j){e=j;f=d(this);g=i}});if(b.range===true&&this.values(1)===b.min){g+=1;f=d(this.handles[g])}if(this._start(a, +g)===false)return false;this._mouseSliding=true;h._handleIndex=g;f.addClass("ui-state-active").focus();b=f.offset();this._clickOffset=!d(a.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:a.pageX-b.left-f.width()/2,top:a.pageY-b.top-f.height()/2-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};this._slide(a,g,c);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(a){var b= +this._normValueFromMouse({x:a.pageX,y:a.pageY});this._slide(a,this._handleIndex,b);return false},_mouseStop:function(a){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(a,this._handleIndex);this._change(a,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b;if(this.orientation==="horizontal"){b= +this.elementSize.width;a=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{b=this.elementSize.height;a=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}b=a/b;if(b>1)b=1;if(b<0)b=0;if(this.orientation==="vertical")b=1-b;a=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+b*a)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b); +c.values=this.values()}return this._trigger("start",a,c)},_slide:function(a,b,c){var e;if(this.options.values&&this.options.values.length){e=this.values(b?0:1);if(this.options.values.length===2&&this.options.range===true&&(b===0&&c>e||b===1&&c1){this.options.values[a]=this._trimAlignValue(b);this._refreshValue();this._change(null,a)}if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;e=arguments[0];for(f=0;fthis._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=a%b;a=a-c;if(Math.abs(c)*2>=b)a+=c>0?b:-b;return parseFloat(a.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var a= +this.options.range,b=this.options,c=this,e=!this._animateOff?b.animate:false,f,h={},g,i,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(k){f=(c.values(k)-c._valueMin())/(c._valueMax()-c._valueMin())*100;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";d(this).stop(1,1)[e?"animate":"css"](h,b.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(k===0)c.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({width:f- +g+"%"},{queue:false,duration:b.animate})}else{if(k===0)c.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({height:f-g+"%"},{queue:false,duration:b.animate})}g=f});else{i=this.value();j=this._valueMin();l=this._valueMax();f=l!==j?(i-j)/(l-j)*100:0;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";this.handle.stop(1,1)[e?"animate":"css"](h,b.animate);if(a==="min"&&this.orientation==="horizontal")this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"}, +b.animate);if(a==="max"&&this.orientation==="horizontal")this.range[e?"animate":"css"]({width:100-f+"%"},{queue:false,duration:b.animate});if(a==="min"&&this.orientation==="vertical")this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},b.animate);if(a==="max"&&this.orientation==="vertical")this.range[e?"animate":"css"]({height:100-f+"%"},{queue:false,duration:b.animate})}}});d.extend(d.ui.slider,{version:"1.8.5"})})(jQuery); +;/* + * jQuery UI Tabs 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function(d,p){function u(){return++v}function w(){return++x}var v=0,x=0;d.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
      ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
    • #{label}
    • "},_create:function(){this._tabify(true)},_setOption:function(a,e){if(a=="selected")this.options.collapsible&& +e==this.options.selected||this.select(e);else{this.options[a]=e;this._tabify()}},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var a=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[a].concat(d.makeArray(arguments)))},_ui:function(a,e){return{tab:a,panel:e,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var a= +d(this);a.html(a.data("label.tabs")).removeData("label.tabs")})},_tabify:function(a){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var b=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]|| +(q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))b.panels=b.panels.add(b._sanitizeSelector(i));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=b._tabId(f);f.href="#"+i;f=d("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(b.panels[g-1]||b.list);f.data("destroy.tabs",true)}b.panels=b.panels.add(f)}else c.disabled.push(g)});if(a){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(b._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected= +this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return b.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); +if(c.selected>=0&&this.anchors.length){this.panels.eq(c.selected).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");b.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[c.selected],b.panels[c.selected]))});this.load(c.selected)}d(window).bind("unload",function(){b.lis.add(b.anchors).unbind(".tabs");b.lis=b.anchors=b.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"));this.element[c.collapsible?"addClass": +"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);a=0;for(var j;j=this.lis[a];a++)d(j)[d.inArray(a,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+g)};this.lis.bind("mouseover.tabs", +function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal",function(){e(f,o);b._trigger("show", +null,b._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");b._trigger("show",null,b._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){b.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);b.element.dequeue("tabs")})}:function(g,f){b.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");b.element.dequeue("tabs")};this.anchors.bind(c.event+".tabs", +function(){var g=this,f=d(g).closest("li"),i=b.panels.filter(":not(.ui-tabs-hide)"),l=d(b._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||b.panels.filter(":animated").length||b._trigger("select",null,b._ui(this,l[0]))===false){this.blur();return false}c.selected=b.anchors.index(this);b.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected=-1;c.cookie&&b._cookie(c.selected,c.cookie);b.element.queue("tabs", +function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&b._cookie(c.selected,c.cookie);b.element.queue("tabs",function(){r(g,l)});b.load(b.anchors.index(this));this.blur();return false}c.cookie&&b._cookie(c.selected,c.cookie);if(l.length){i.length&&b.element.queue("tabs",function(){s(g,i)});b.element.queue("tabs",function(){r(g,l)});b.load(b.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier.";d.browser.msie&&this.blur()});this.anchors.bind("click.tabs", +function(){return false})},_getIndex:function(a){if(typeof a=="string")a=this.anchors.index(this.anchors.filter("[href$="+a+"]"));return a},destroy:function(){var a=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e=d.data(this,"href.tabs");if(e)this.href= +e;var b=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){b.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});a.cookie&&this._cookie(null,a.cookie);return this},add:function(a,e,b){if(b===p)b=this.anchors.length; +var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,a).replace(/#\{label\}/g,e));a=!a.indexOf("#")?a.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=d("#"+a);j.length||(j=d(h.panelTemplate).attr("id",a).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(b>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[b]); +j.insertBefore(this.panels[b])}h.disabled=d.map(h.disabled,function(k){return k>=b?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[b],this.panels[b]));return this},remove:function(a){a=this._getIndex(a);var e=this.options,b=this.lis.eq(a).remove(),c=this.panels.eq(a).remove(); +if(b.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(a+(a+1=a?--h:h});this._tabify();this._trigger("remove",null,this._ui(b.find("a")[0],c[0]));return this},enable:function(a){a=this._getIndex(a);var e=this.options;if(d.inArray(a,e.disabled)!=-1){this.lis.eq(a).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(b){return b!=a});this._trigger("enable",null, +this._ui(this.anchors[a],this.panels[a]));return this}},disable:function(a){a=this._getIndex(a);var e=this.options;if(a!=e.selected){this.lis.eq(a).addClass("ui-state-disabled");e.disabled.push(a);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))}return this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;this.anchors.eq(a).trigger(this.options.event+".tabs");return this}, +load:function(a){a=this._getIndex(a);var e=this,b=this.options,c=this.anchors.eq(a)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(a).addClass("ui-state-processing");if(b.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(b.spinner)}this.xhr=d.ajax(d.extend({},b.ajaxOptions,{url:h,success:function(k,n){d(e._sanitizeSelector(c.hash)).html(k);e._cleanup();b.cache&&d.data(c,"cache.tabs", +true);e._trigger("load",null,e._ui(e.anchors[a],e.panels[a]));try{b.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[a],e.panels[a]));try{b.ajaxOptions.error(k,n,a,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this},url:function(a, +e){this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.5"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(a,e){var b=this,c=this.options,h=b._rotate||(b._rotate=function(j){clearTimeout(b.rotation);b.rotation=setTimeout(function(){var k=c.selected;b.select(++k')}function E(a,b){d.extend(a, +b);for(var c in b)if(b[c]==null||b[c]==G)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.5"}});var y=(new Date).getTime();d.extend(L.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){E(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]= +f}}}e=a.nodeName.toLowerCase();f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:d('
      ')}}, +_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&& +b.append.remove();if(c){b.append=d(''+c+"");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('').addClass(this._triggerClass).html(f== +""?c:d("").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;gh){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a, +c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b), +true);this._updateDatepicker(b);this._updateAlternate(b)}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}E(a.settings,e||{});b=b&&b.constructor== +Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]); +d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}}, +_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b= +d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false; +for(var b=0;b-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},_showDatepicker:function(a){a=a.target|| +a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);d.datepicker._curInst&&d.datepicker._curInst!=b&&d.datepicker._curInst.dpDiv.stop(true,true);var c=d.datepicker._get(b,"beforeShow");E(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos=d.datepicker._findPos(a); +d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b,c,e);b.dpDiv.css({position:d.datepicker._inDialog&& +d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){d.datepicker._datepickerShowing=true;var i=d.datepicker._getBorders(b.dpDiv);b.dpDiv.find("iframe.ui-datepicker-cover").css({left:-i[0],top:-i[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})};b.dpDiv.zIndex(d(a).zIndex()+1);d.effects&&d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f, +h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this,c=d.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a)).find("iframe.ui-datepicker-cover").css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){d(this).removeClass("ui-state-hover"); +this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!b._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){d(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");d(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).addClass("ui-datepicker-prev-hover"); +this.className.indexOf("ui-datepicker-next")!=-1&&d(this).addClass("ui-datepicker-next-hover")}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();c=this._getNumberOfMonths(a);var e=c[1];e>1?a.dpDiv.addClass("ui-datepicker-multi-"+e).css("width",17*e+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(c[0]!=1||c[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"); +a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input.focus()},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(), +k=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-g):0);b.top-=Math.min(b.top,b.top+f>k&&k>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1);)a=a[b?"previousSibling":"nextSibling"]; +a=d(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]?b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();if(a=this._get(b,"onClose"))a.apply(b.input?b.input[0]:null,[b.input?b.input.val(): +"",b]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&& +!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth; +b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear=false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var b= +this._getInst(d(a)[0]);b.input&&b._selectingMonthYear&&setTimeout(function(){b.input.focus()},0);b._selectingMonthYear=!b._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a= +d(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a, +"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b== +"object"?b.toString():b+"";if(b=="")return null;for(var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff,f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,k=c=-1,l=-1,u=-1,j=false,o=function(p){(p=z+1 +-1){k=1;l=u;do{e=this._getDaysInMonth(c,k-1);if(l<=e)break;k++;l-=e}while(1)}v=this._daylightSavingAdjust(new Date(c,k-1,l));if(v.getFullYear()!=c||v.getMonth()+1!=k||v.getDate()!=l)throw"Invalid date";return v},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24* +60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=j+112?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e? +"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),k= +this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),j=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n=this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=j&&nn;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a, +"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-k,1)),this._getFormatConfig(a));n=this._canAdjustMonth(a,-1,m,g)?''+n+"":f?"":''+ +n+"";var r=this._get(a,"nextText");r=!h?r:this.formatDate(r,this._daylightSavingAdjust(new Date(m,g+k,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?''+r+"":f?"":''+r+"";k=this._get(a,"currentText");r=this._get(a,"gotoCurrent")&&a.currentDay?u:b;k=!h?k:this.formatDate(k,r,this._getFormatConfig(a));h=!a.inline?'":"";e=e?'
      '+(c?h:"")+(this._isInRange(a,r)?'":"")+(c?"":h)+"
      ":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;k=this._get(a,"showWeek");r=this._get(a,"dayNames");this._get(a,"dayNamesShort");var s=this._get(a,"dayNamesMin"),z=this._get(a,"monthNames"),v=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),w=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var M=this._getDefaultDate(a),I="",C=0;C1)switch(D){case 0:x+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]-1:x+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:x+=" ui-datepicker-group-middle";t="";break}x+='">'}x+='
      '+(/all|left/.test(t)&&C==0?c? +f:n:"")+(/all|right/.test(t)&&C==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,j,o,C>0||D>0,z,v)+'
      ';var A=k?'":"";for(t=0;t<7;t++){var q=(t+h)%7;A+="=5?' class="ui-datepicker-week-end"':"")+'>'+s[q]+""}x+=A+"";A=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay, +A);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;A=l?6:Math.ceil((t+A)/7);q=this._daylightSavingAdjust(new Date(m,g,1-t));for(var O=0;O";var P=!k?"":'";for(t=0;t<7;t++){var F=p?p.apply(a.input?a.input[0]:null,[q]):[true,""],B=q.getMonth()!=g,K=B&&!H||!F[0]||j&&qo;P+='";q.setDate(q.getDate()+1);q=this._daylightSavingAdjust(q)}x+=P+""}g++;if(g>11){g=0;m++}x+="
      '+this._get(a,"weekHeader")+"
      '+this._get(a,"calculateWeek")(q)+""+(B&&!w?" ":K?''+q.getDate()+ +"":''+q.getDate()+"")+"
      "+(l?""+(i[0]>0&&D==i[1]-1?'
      ':""):"");N+=x}I+=N}I+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'': +"");a._keyEvent=false;return I},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var k=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),j='
      ',o="";if(h||!k)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(j+=o+(h||!(k&&l)?" ":""));if(h||!l)j+=''+c+"";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b, +i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(j+='"}j+=this._get(a,"yearSuffix");if(u)j+=(h||!(k&&l)?" ":"")+o;j+="
      ";return j},_adjustInstDate:function(a,b,c){var e= +a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a, +"onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a); +c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a, +"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker= +function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b)); +return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new L;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.5";window["DP_jQuery_"+y]=d})(jQuery); +;/* + * jQuery UI Progressbar 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function(b,c){b.widget("ui.progressbar",{options:{value:0},min:0,max:100,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.max,"aria-valuenow":this._value()});this.valueDiv=b("
      ").appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"); +this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===c)return this._value();this._setOption("value",a);return this},_setOption:function(a,d){if(a==="value"){this.options.value=d;this._refreshValue();this._trigger("change")}b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;if(typeof a!=="number")a=0;return Math.min(this.max,Math.max(this.min,a))},_refreshValue:function(){var a=this.value();this.valueDiv.toggleClass("ui-corner-right", +a===this.max).width(a+"%");this.element.attr("aria-valuenow",a)}});b.extend(b.ui.progressbar,{version:"1.8.5"})})(jQuery); +;/* + * jQuery UI Effects 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/ + */ +jQuery.effects||function(f,j){function l(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1], +16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return m.transparent;return m[f.trim(c).toLowerCase()]}function r(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return l(b)}function n(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle, +a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function o(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in s||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function t(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d= +a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:f.fx.speeds[b]||f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=r(b.elem,a);b.end=l(b.end);b.colorInit= +true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var m={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189, +183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255, +165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},p=["add","remove","toggle"],s={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b,d){if(f.isFunction(b)){d=b;b=null}return this.each(function(){var e=f(this),g=e.attr("style")||" ",h=o(n.call(this)),q,u=e.attr("className");f.each(p,function(v, +i){c[i]&&e[i+"Class"](c[i])});q=o(n.call(this));e.attr("className",u);e.animate(t(h,q),a,b,function(){f.each(p,function(v,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments)})})};f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a? +f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this,[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.5",save:function(c,a){for(var b=0;b").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"}); +c.css({position:"relative",top:0,left:0})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments);a={options:a[1],duration:a[2],callback:a[3]};var b=f.effects[c];return b&&!f.fx.off?b.call(this,a):this},_show:f.fn.show,show:function(c){if(!c|| +typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._show.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c]||typeof c== +"boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c, +a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/= +e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+ +b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/ +2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+ +e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery); +;/* + * jQuery UI Effects Fade 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fade + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Fold 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * jquery.effects.core.js + */ +(function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","left"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1],10)/100* +f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery); +;/* + * jQuery UI Effects Highlight 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Highlight + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&& +this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Pulsate 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Pulsate + * + * Depends: + * jquery.effects.core.js + */ +(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments); +b.dequeue()})})}})(jQuery); +; \ No newline at end of file diff --git a/web/js/globals/jquery.colorbox-min.js b/web/js/globals/jquery.colorbox-min.js new file mode 100755 index 0000000..ec76aa9 --- /dev/null +++ b/web/js/globals/jquery.colorbox-min.js @@ -0,0 +1,2 @@ +/* ColorBox v1.3.6 - a full featured, light-weight, customizable lightbox based on jQuery 1.3 */ +(function(c){function r(b,d){d=d==="x"?m.width():m.height();return typeof b==="string"?Math.round(b.match(/%/)?d/100*parseInt(b,10):parseInt(b,10)):b}function M(b){b=c.isFunction(b)?b.call(i):b;return a.photo||b.match(/\.(gif|png|jpg|jpeg|bmp)(?:\?([^#]*))?(?:#(\.*))?$/i)}function Y(){for(var b in a)if(c.isFunction(a[b])&&b.substring(0,2)!=="on")a[b]=a[b].call(i);a.rel=a.rel||i.rel;a.href=a.href||i.href;a.title=a.title||i.title}function Z(b){i=b;a=c.extend({},c(i).data(q));Y();if(a.rel&&a.rel!=="nofollow"){g= c(".cboxElement").filter(function(){return(c(this).data(q).rel||this.rel)===a.rel});j=g.index(i);if(j<0){g=g.add(i);j=g.length-1}}else{g=c(i);j=0}if(!B){C=B=n;N=i;N.blur();c(document).bind("keydown.cbox_close",function(d){if(d.keyCode===27){d.preventDefault();e.close()}}).bind("keydown.cbox_arrows",function(d){if(g.length>1)if(d.keyCode===37){d.preventDefault();D.click()}else if(d.keyCode===39){d.preventDefault();E.click()}});a.overlayClose&&s.css({cursor:"pointer"}).one("click",e.close);c.event.trigger(aa); a.onOpen&&a.onOpen.call(i);s.css({opacity:a.opacity}).show();a.w=r(a.initialWidth,"x");a.h=r(a.initialHeight,"y");e.position(0);O&&m.bind("resize.cboxie6 scroll.cboxie6",function(){s.css({width:m.width(),height:m.height(),top:m.scrollTop(),left:m.scrollLeft()})}).trigger("scroll.cboxie6")}P.add(D).add(E).add(t).add(Q).hide();R.html(a.close).show();e.slideshow();e.load()}var q="colorbox",F="hover",n=true,e,x=c.browser.msie&&!c.support.opacity,O=x&&c.browser.version<7,aa="cbox_open",H="cbox_load",S= "cbox_complete",T="resize.cbox_resize",s,k,u,p,U,V,W,X,g,m,l,I,J,K,Q,P,t,E,D,R,y,z,v,w,i,N,j,a,B,C,$={transition:"elastic",speed:350,width:false,height:false,innerWidth:false,innerHeight:false,initialWidth:"400",initialHeight:"400",maxWidth:false,maxHeight:false,scalePhotos:n,scrolling:n,inline:false,html:false,iframe:false,photo:false,href:false,title:false,rel:false,opacity:0.9,preloading:n,current:"image {current} of {total}",previous:"previous",next:"next",close:"close",open:false,overlayClose:n, slideshow:false,slideshowAuto:n,slideshowSpeed:2500,slideshowStart:"start slideshow",slideshowStop:"stop slideshow",onOpen:false,onLoad:false,onComplete:false,onCleanup:false,onClosed:false};e=c.fn.colorbox=function(b,d){var h=this;if(!h.length)if(h.selector===""){h=c("");b.open=n}else return this;h.each(function(){var f=c.extend({},c(this).data(q)?c(this).data(q):$,b);c(this).data(q,f).addClass("cboxElement");if(d)c(this).data(q).onComplete=d});b&&b.open&&Z(h);return this};e.init=function(){function b(d){return c('
      ')}m=c(window);k=c('
      ');s=b("Overlay").hide();u=b("Wrapper");p=b("Content").append(l=b("LoadedContent").css({width:0,height:0}),J=b("LoadingOverlay"),K=b("LoadingGraphic"),Q=b("Title"),P=b("Current"),t=b("Slideshow"),E=b("Next"),D=b("Previous"),R=b("Close"));u.append(c("
      ").append(b("TopLeft"),U=b("TopCenter"),b("TopRight")),c("
      ").append(V=b("MiddleLeft"),p,W=b("MiddleRight")),c("
      ").append(b("BottomLeft"),X=b("BottomCenter"),b("BottomRight"))).children().children().css({"float":"left"}); I=c("
      ");c("body").prepend(s,k.append(u,I));if(x){k.addClass("cboxIE");O&&s.css("position","absolute")}p.children().bind("mouseover mouseout",function(){c(this).toggleClass(F)}).addClass(F);y=U.height()+X.height()+p.outerHeight(n)-p.height();z=V.width()+W.width()+p.outerWidth(n)-p.width();v=l.outerHeight(n);w=l.outerWidth(n);k.css({"padding-bottom":y,"padding-right":z}).hide();E.click(e.next);D.click(e.prev);R.click(e.close);p.children().removeClass(F); c(".cboxElement").live("click",function(d){if(d.button!==0&&typeof d.button!=="undefined")return n;else{Z(this);return false}})};e.position=function(b,d){function h(A){U[0].style.width=X[0].style.width=p[0].style.width=A.style.width;K[0].style.height=J[0].style.height=p[0].style.height=V[0].style.height=W[0].style.height=A.style.height}var f=m.height();f=Math.max(f-a.h-v-y,0)/2+m.scrollTop();var o=Math.max(document.documentElement.clientWidth-a.w-w-z,0)/2+m.scrollLeft();b=k.width()===a.w+w&&k.height()=== a.h+v?0:b;u[0].style.width=u[0].style.height="9999px";k.dequeue().animate({width:a.w+w,height:a.h+v,top:f,left:o},{duration:b,complete:function(){h(this);C=false;u[0].style.width=a.w+w+z+"px";u[0].style.height=a.h+v+y+"px";d&&d()},step:function(){h(this)}})};e.resize=function(b){function d(){a.w=a.w||l.width();a.w=a.mw&&a.mw");l.show();Q.show().html(a.title);if(g.length>1){P.html(a.current.replace(/\{current\}/,j+1).replace(/\{total\}/,g.length)).show();E.html(a.next).show();D.html(a.previous).show();a.slideshow&&t.show()}J.hide();K.hide();c.event.trigger(S);a.onComplete&&a.onComplete.call(i);a.transition==="fade"&&k.fadeTo(L,1,function(){x&&k[0].style.removeAttribute("filter")}); m.bind(T,function(){e.position(0)})}})}if(B){var o,A,L=a.transition==="none"?0:a.speed;m.unbind(T);if(b){l.remove();l=c('
      ').html(b);l.hide().appendTo(I).css({width:d(),overflow:a.scrolling?"auto":"hidden"}).css({height:h()}).prependTo(p);c("#cboxPhoto").css({cssFloat:"none"});O&&c("select:not(#colorbox select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("cbox_cleanup",function(){this.style.visibility="inherit"});a.transition=== "fade"&&k.fadeTo(L,0,function(){f(0)})||f(L);if(a.preloading&&g.length>1){b=j>0?g[j-1]:g[g.length-1];o=j").attr("src",o);M(b)&&c("").attr("src",b)}}else setTimeout(function(){var G=l.wrapInner("
      ").children();a.h=G.height();l.css({height:a.h});G.replaceWith(G.children());e.position(L)},1)}};e.load=function(){var b,d,h,f=e.resize;C=n;i=g[j];a=c.extend({},c(i).data(q)); Y();c.event.trigger(H);a.onLoad&&a.onLoad.call(i);a.h=a.height?r(a.height,"y")-v-y:a.innerHeight?r(a.innerHeight,"y"):false;a.w=a.width?r(a.width,"x")-w-z:a.innerWidth?r(a.innerWidth,"x"):false;a.mw=a.w;a.mh=a.h;if(a.maxWidth){a.mw=r(a.maxWidth,"x")-w-z;a.mw=a.w&&a.w').hide().insertBefore(c(b)[0]).bind(H+" cbox_cleanup",function(){c(this).replaceWith(l.children())}); f(c(b))}else if(a.iframe)f(" ");else if(a.html)f(a.html);else if(M(b)){d=new Image;d.onload=function(){var o;d.onload=null;d.id="cboxPhoto";c(d).css({margin:"auto",border:"none",display:"block",cssFloat:"left"});if(a.scalePhotos){h=function(){d.height-=d.height*o;d.width-=d.width*o};if(a.mw&&d.width>a.mw){o=(d.width-a.mw)/d.width;h()}if(a.mh&&d.height>a.mh){o=(d.height-a.mh)/d.height;h()}}if(a.h)d.style.marginTop=Math.max(a.h-d.height,0)/2+"px";f(d);g.length>1&&c(d).css({cursor:"pointer"}).click(e.next); if(x)d.style.msInterpolationMode="bicubic"};d.src=b}else c("
      ").appendTo(I).load(b,function(o,A){A==="success"?f(this):f(c("

      Request unsuccessful.

      "))})};e.next=function(){if(!C){j=j0?j-1:g.length-1;e.load()}};e.slideshow=function(){function b(){t.text(a.slideshowStop).bind(S,function(){h=setTimeout(e.next,a.slideshowSpeed)}).bind(H,function(){clearTimeout(h)}).one("click",function(){d();c(this).removeClass(F)});k.removeClass(f+ "off").addClass(f+"on")}var d,h,f="cboxSlideshow_";t.bind("cbox_closed",function(){t.unbind();clearTimeout(h);k.removeClass(f+"off "+f+"on")});d=function(){clearTimeout(h);t.text(a.slideshowStart).unbind(S+" "+H).one("click",function(){b();h=setTimeout(e.next,a.slideshowSpeed);c(this).removeClass(F)});k.removeClass(f+"on").addClass(f+"off")};if(a.slideshow&&g.length>1)a.slideshowAuto?b():d()};e.close=function(){c.event.trigger("cbox_cleanup");a.onCleanup&&a.onCleanup.call(i);B=false;c(document).unbind("keydown.cbox_close keydown.cbox_arrows"); m.unbind(T+" resize.cboxie6 scroll.cboxie6");s.css({cursor:"auto"}).fadeOut("fast");k.stop(n,false).fadeOut("fast",function(){c("#colorbox iframe").attr("src","about:blank");l.remove();k.css({opacity:1});try{N.focus()}catch(b){}c.event.trigger("cbox_closed");a.onClosed&&a.onClosed.call(i)})};e.element=function(){return c(i)};e.settings=$;c(e.init)})(jQuery); \ No newline at end of file diff --git a/web/js/globals/jquery.min.js b/web/js/globals/jquery.min.js new file mode 100755 index 0000000..7c24308 --- /dev/null +++ b/web/js/globals/jquery.min.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
      a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="
      "; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

      ";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
      ";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
      ","
      "];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
      ").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
      "; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/web/js/globals/tinymce/jquery.tinymce.js b/web/js/globals/tinymce/jquery.tinymce.js new file mode 100644 index 0000000..4866c2a --- /dev/null +++ b/web/js/globals/tinymce/jquery.tinymce.js @@ -0,0 +1 @@ +(function(b){var e,d,a=[],c=window;b.fn.tinymce=function(j){var p=this,g,k,h,m,i,l="",n="";if(!p.length){return p}if(!j){return tinyMCE.get(p[0].id)}function o(){var r=[],q=0;if(f){f();f=null}p.each(function(t,u){var s,w=u.id,v=j.oninit;if(!w){u.id=w=tinymce.DOM.uniqueId()}s=new tinymce.Editor(w,j);r.push(s);if(v){s.onInit.add(function(){var x,y=v;if(++q==r.length){if(tinymce.is(y,"string")){x=(y.indexOf(".")===-1)?null:tinymce.resolve(y.replace(/\.\w+$/,""));y=tinymce.resolve(y)}y.apply(x||tinymce,r)}})}});b.each(r,function(t,s){s.render()})}if(!c.tinymce&&!d&&(g=j.script_url)){d=1;h=g.substring(0,g.lastIndexOf("/"));if(/_(src|dev)\.js/g.test(g)){n="_src"}m=g.lastIndexOf("?");if(m!=-1){l=g.substring(m+1)}c.tinyMCEPreInit=c.tinyMCEPreInit||{base:h,suffix:n,query:l};if(g.indexOf("gzip")!=-1){i=j.language||"en";g=g+(/\?/.test(g)?"&":"?")+"js=true&core=true&suffix="+escape(n)+"&themes="+escape(j.theme)+"&plugins="+escape(j.plugins)+"&languages="+i;if(!c.tinyMCE_GZ){tinyMCE_GZ={start:function(){tinymce.suffix=n;function q(r){tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(r))}q("langs/"+i+".js");q("themes/"+j.theme+"/editor_template"+n+".js");q("themes/"+j.theme+"/langs/"+i+".js");b.each(j.plugins.split(","),function(s,r){if(r){q("plugins/"+r+"/editor_plugin"+n+".js");q("plugins/"+r+"/langs/"+i+".js")}})},end:function(){}}}}b.ajax({type:"GET",url:g,dataType:"script",cache:true,success:function(){tinymce.dom.Event.domLoaded=1;d=2;if(j.script_loaded){j.script_loaded()}o();b.each(a,function(q,r){r()})}})}else{if(d===1){a.push(o)}else{o()}}return p};b.extend(b.expr[":"],{tinymce:function(g){return g.id&&!!tinyMCE.get(g.id)}});function f(){function i(l){if(l==="remove"){this.each(function(n,o){var m=h(o);if(m){m.remove()}})}this.find("span.mceEditor,div.mceEditor").each(function(n,o){var m=tinyMCE.get(o.id.replace(/_parent$/,""));if(m){m.remove()}})}function k(n){var m=this,l;if(n!==e){i.call(m);m.each(function(p,q){var o;if(o=tinyMCE.get(q.id)){o.setContent(n)}})}else{if(m.length>0){if(l=tinyMCE.get(m[0].id)){return l.getContent()}}}}function h(m){var l=null;(m)&&(m.id)&&(c.tinymce)&&(l=tinyMCE.get(m.id));return l}function g(l){return !!((l)&&(l.length)&&(c.tinymce)&&(l.is(":tinymce")))}var j={};b.each(["text","html","val"],function(n,l){var o=j[l]=b.fn[l],m=(l==="text");b.fn[l]=function(s){var p=this;if(!g(p)){return o.apply(p,arguments)}if(s!==e){k.call(p.filter(":tinymce"),s);o.apply(p.not(":tinymce"),arguments);return p}else{var r="";var q=arguments;(m?p:p.eq(0)).each(function(u,v){var t=h(v);r+=t?(m?t.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):t.getContent()):o.apply(b(v),q)});return r}}});b.each(["append","prepend"],function(n,m){var o=j[m]=b.fn[m],l=(m==="prepend");b.fn[m]=function(q){var p=this;if(!g(p)){return o.apply(p,arguments)}if(q!==e){p.filter(":tinymce").each(function(s,t){var r=h(t);r&&r.setContent(l?q+r.getContent():r.getContent()+q)});o.apply(p.not(":tinymce"),arguments);return p}}});b.each(["remove","replaceWith","replaceAll","empty"],function(m,l){var n=j[l]=b.fn[l];b.fn[l]=function(){i.call(this,l);return n.apply(this,arguments)}});j.attr=b.fn.attr;b.fn.attr=function(n,q,o){var m=this;if((!n)||(n!=="value")||(!g(m))){return j.attr.call(m,n,q,o)}if(q!==e){k.call(m.filter(":tinymce"),q);j.attr.call(m.not(":tinymce"),n,q,o);return m}else{var p=m[0],l=h(p);return l?l.getContent():j.attr.call(b(p),n,q,o)}}}})(jQuery); \ No newline at end of file diff --git a/web/js/globals/tinymce/langs/en.js b/web/js/globals/tinymce/langs/en.js new file mode 100644 index 0000000..53a421c --- /dev/null +++ b/web/js/globals/tinymce/langs/en.js @@ -0,0 +1,170 @@ +tinyMCE.addI18n({en:{ +common:{ +edit_confirm:"Do you want to use the WYSIWYG mode for this textarea?", +apply:"Apply", +insert:"Insert", +update:"Update", +cancel:"Cancel", +close:"Close", +browse:"Browse", +class_name:"Class", +not_set:"-- Not set --", +clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?", +clipboard_no_support:"Currently not supported by your browser, use keyboard shortcuts instead.", +popup_blocked:"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this project in order to fully utilize this tool.", +invalid_data:"Error: Invalid values entered, these are marked in red.", +more_colors:"More colors" +}, +contextmenu:{ +align:"Alignment", +left:"Left", +center:"Center", +right:"Right", +full:"Full" +}, +insertdatetime:{ +date_fmt:"%Y-%m-%d", +time_fmt:"%H:%M:%S", +insertdate_desc:"Insert date", +inserttime_desc:"Insert time", +months_long:"January,February,March,April,May,June,July,August,September,October,November,December", +months_short:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", +day_long:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday", +day_short:"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun" +}, +print:{ +print_desc:"Print" +}, +preview:{ +preview_desc:"Preview" +}, +directionality:{ +ltr_desc:"Direction left to right", +rtl_desc:"Direction right to left" +}, +layer:{ +insertlayer_desc:"Insert new layer", +forward_desc:"Move forward", +backward_desc:"Move backward", +absolute_desc:"Toggle absolute positioning", +content:"New layer..." +}, +save:{ +save_desc:"Save", +cancel_desc:"Cancel all changes" +}, +nonbreaking:{ +nonbreaking_desc:"Insert non-breaking space character" +}, +iespell:{ +iespell_desc:"Run spell checking", +download:"ieSpell not detected. Do you want to install it now?" +}, +advhr:{ +advhr_desc:"Horizontal rule" +}, +emotions:{ +emotions_desc:"Emotions" +}, +searchreplace:{ +search_desc:"Find", +replace_desc:"Find/Replace" +}, +advimage:{ +image_desc:"Insert/edit image" +}, +advlink:{ +link_desc:"Insert/edit link" +}, +xhtmlxtras:{ +cite_desc:"Citation", +abbr_desc:"Abbreviation", +acronym_desc:"Acronym", +del_desc:"Deletion", +ins_desc:"Insertion", +attribs_desc:"Insert/Edit Attributes" +}, +style:{ +desc:"Edit CSS Style" +}, +paste:{ +paste_text_desc:"Paste as Plain Text", +paste_word_desc:"Paste from Word", +selectall_desc:"Select All", +plaintext_mode_sticky:"Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.", +plaintext_mode:"Paste is now in plain text mode. Click again to toggle back to regular paste mode." +}, +paste_dlg:{ +text_title:"Use CTRL+V on your keyboard to paste the text into the window.", +text_linebreaks:"Keep linebreaks", +word_title:"Use CTRL+V on your keyboard to paste the text into the window." +}, +table:{ +desc:"Inserts a new table", +row_before_desc:"Insert row before", +row_after_desc:"Insert row after", +delete_row_desc:"Delete row", +col_before_desc:"Insert column before", +col_after_desc:"Insert column after", +delete_col_desc:"Remove column", +split_cells_desc:"Split merged table cells", +merge_cells_desc:"Merge table cells", +row_desc:"Table row properties", +cell_desc:"Table cell properties", +props_desc:"Table properties", +paste_row_before_desc:"Paste table row before", +paste_row_after_desc:"Paste table row after", +cut_row_desc:"Cut table row", +copy_row_desc:"Copy table row", +del:"Delete table", +row:"Row", +col:"Column", +cell:"Cell" +}, +autosave:{ +unload_msg:"The changes you made will be lost if you navigate away from this page.", +restore_content:"Restore auto-saved content.", +warning_message:"If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?." +}, +fullscreen:{ +desc:"Toggle fullscreen mode" +}, +media:{ +desc:"Insert / edit embedded media", +edit:"Edit embedded media" +}, +fullpage:{ +desc:"Document properties" +}, +template:{ +desc:"Insert predefined template content" +}, +visualchars:{ +desc:"Visual control characters on/off." +}, +spellchecker:{ +desc:"Toggle spellchecker", +menu:"Spellchecker settings", +ignore_word:"Ignore word", +ignore_words:"Ignore all", +langs:"Languages", +wait:"Please wait...", +sug:"Suggestions", +no_sug:"No suggestions", +no_mpell:"No misspellings found." +}, +pagebreak:{ +desc:"Insert page break." +}, +advlist:{ +types:"Types", +def:"Default", +lower_alpha:"Lower alpha", +lower_greek:"Lower greek", +lower_roman:"Lower roman", +upper_alpha:"Upper alpha", +upper_roman:"Upper roman", +circle:"Circle", +disc:"Disc", +square:"Square" +}}}); \ No newline at end of file diff --git a/web/js/globals/tinymce/license.txt b/web/js/globals/tinymce/license.txt new file mode 100644 index 0000000..60d6d4c --- /dev/null +++ b/web/js/globals/tinymce/license.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/web/js/globals/tinymce/plugins/advhr/css/advhr.css b/web/js/globals/tinymce/plugins/advhr/css/advhr.css new file mode 100644 index 0000000..0e22834 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advhr/css/advhr.css @@ -0,0 +1,5 @@ +input.radio {border:1px none #000; background:transparent; vertical-align:middle;} +.panel_wrapper div.current {height:80px;} +#width {width:50px; vertical-align:middle;} +#width2 {width:50px; vertical-align:middle;} +#size {width:100px;} diff --git a/web/js/globals/tinymce/plugins/advhr/editor_plugin.js b/web/js/globals/tinymce/plugins/advhr/editor_plugin.js new file mode 100644 index 0000000..4d3b062 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advhr/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.AdvancedHRPlugin",{init:function(a,b){a.addCommand("mceAdvancedHr",function(){a.windowManager.open({file:b+"/rule.htm",width:250+parseInt(a.getLang("advhr.delta_width",0)),height:160+parseInt(a.getLang("advhr.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("advhr",{title:"advhr.advhr_desc",cmd:"mceAdvancedHr"});a.onNodeChange.add(function(d,c,e){c.setActive("advhr",e.nodeName=="HR")});a.onClick.add(function(c,d){d=d.target;if(d.nodeName==="HR"){c.selection.select(d)}})},getInfo:function(){return{longname:"Advanced HR",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advhr",tinymce.plugins.AdvancedHRPlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advhr/editor_plugin_src.js b/web/js/globals/tinymce/plugins/advhr/editor_plugin_src.js new file mode 100644 index 0000000..0c652d3 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advhr/editor_plugin_src.js @@ -0,0 +1,57 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.AdvancedHRPlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceAdvancedHr', function() { + ed.windowManager.open({ + file : url + '/rule.htm', + width : 250 + parseInt(ed.getLang('advhr.delta_width', 0)), + height : 160 + parseInt(ed.getLang('advhr.delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register buttons + ed.addButton('advhr', { + title : 'advhr.advhr_desc', + cmd : 'mceAdvancedHr' + }); + + ed.onNodeChange.add(function(ed, cm, n) { + cm.setActive('advhr', n.nodeName == 'HR'); + }); + + ed.onClick.add(function(ed, e) { + e = e.target; + + if (e.nodeName === 'HR') + ed.selection.select(e); + }); + }, + + getInfo : function() { + return { + longname : 'Advanced HR', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('advhr', tinymce.plugins.AdvancedHRPlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advhr/js/rule.js b/web/js/globals/tinymce/plugins/advhr/js/rule.js new file mode 100644 index 0000000..b6cbd66 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advhr/js/rule.js @@ -0,0 +1,43 @@ +var AdvHRDialog = { + init : function(ed) { + var dom = ed.dom, f = document.forms[0], n = ed.selection.getNode(), w; + + w = dom.getAttrib(n, 'width'); + f.width.value = w ? parseInt(w) : (dom.getStyle('width') || ''); + f.size.value = dom.getAttrib(n, 'size') || parseInt(dom.getStyle('height')) || ''; + f.noshade.checked = !!dom.getAttrib(n, 'noshade') || !!dom.getStyle('border-width'); + selectByValue(f, 'width2', w.indexOf('%') != -1 ? '%' : 'px'); + }, + + update : function() { + var ed = tinyMCEPopup.editor, h, f = document.forms[0], st = ''; + + h = ' + + + {#advhr.advhr_desc} + + + + + + + +
      + + +
      +
      + + + + + + + + + + + + + +
      + + +
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/advimage/css/advimage.css b/web/js/globals/tinymce/plugins/advimage/css/advimage.css new file mode 100644 index 0000000..0a6251a --- /dev/null +++ b/web/js/globals/tinymce/plugins/advimage/css/advimage.css @@ -0,0 +1,13 @@ +#src_list, #over_list, #out_list {width:280px;} +.mceActionPanel {margin-top:7px;} +.alignPreview {border:1px solid #000; width:140px; height:140px; overflow:hidden; padding:5px;} +.checkbox {border:0;} +.panel_wrapper div.current {height:305px;} +#prev {margin:0; border:1px solid #000; width:428px; height:150px; overflow:auto;} +#align, #classlist {width:150px;} +#width, #height {vertical-align:middle; width:50px; text-align:center;} +#vspace, #hspace, #border {vertical-align:middle; width:30px; text-align:center;} +#class_list {width:180px;} +input {width: 280px;} +#constrain, #onmousemovecheck {width:auto;} +#id, #dir, #lang, #usemap, #longdesc {width:200px;} diff --git a/web/js/globals/tinymce/plugins/advimage/editor_plugin.js b/web/js/globals/tinymce/plugins/advimage/editor_plugin.js new file mode 100644 index 0000000..4c7a9c3 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advimage/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.AdvancedImagePlugin",{init:function(a,b){a.addCommand("mceAdvImage",function(){if(a.dom.getAttrib(a.selection.getNode(),"class").indexOf("mceItem")!=-1){return}a.windowManager.open({file:b+"/image.htm",width:480+parseInt(a.getLang("advimage.delta_width",0)),height:385+parseInt(a.getLang("advimage.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("image",{title:"advimage.image_desc",cmd:"mceAdvImage"})},getInfo:function(){return{longname:"Advanced image",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advimage",tinymce.plugins.AdvancedImagePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advimage/editor_plugin_src.js b/web/js/globals/tinymce/plugins/advimage/editor_plugin_src.js new file mode 100644 index 0000000..2625dd2 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advimage/editor_plugin_src.js @@ -0,0 +1,50 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.AdvancedImagePlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceAdvImage', function() { + // Internal image object like a flash placeholder + if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1) + return; + + ed.windowManager.open({ + file : url + '/image.htm', + width : 480 + parseInt(ed.getLang('advimage.delta_width', 0)), + height : 385 + parseInt(ed.getLang('advimage.delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register buttons + ed.addButton('image', { + title : 'advimage.image_desc', + cmd : 'mceAdvImage' + }); + }, + + getInfo : function() { + return { + longname : 'Advanced image', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('advimage', tinymce.plugins.AdvancedImagePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advimage/image.htm b/web/js/globals/tinymce/plugins/advimage/image.htm new file mode 100644 index 0000000..79cff3f --- /dev/null +++ b/web/js/globals/tinymce/plugins/advimage/image.htm @@ -0,0 +1,232 @@ + + + + {#advimage_dlg.dialog_title} + + + + + + + + + +
      + + +
      +
      +
      + {#advimage_dlg.general} + + + + + + + + + + + + + + + + + + +
      + + + + +
       
      +
      + +
      + {#advimage_dlg.preview} + +
      +
      + +
      +
      + {#advimage_dlg.tab_appearance} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + {#advimage_dlg.example_img} + Lorem ipsum, Dolor sit amet, consectetuer adipiscing loreum ipsum edipiscing elit, sed diam + nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.Loreum ipsum + edipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam + erat volutpat. +
      +
      + x + px +
        + + + + +
      +
      +
      +
      + +
      +
      + {#advimage_dlg.swap_image} + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
       
      + + + + +
       
      +
      + +
      + {#advimage_dlg.misc} + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + +
      + +
      + + + + +
       
      +
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/advimage/img/sample.gif b/web/js/globals/tinymce/plugins/advimage/img/sample.gif new file mode 100644 index 0000000000000000000000000000000000000000..53bf6890b507741c10910c9e2217ad8247b98e8d GIT binary patch literal 1624 zcmV-e2B-N)Nk%w1VJ!eH0OkMy|NsB}{r&v>{Q3F$`1ttq^YifV@ayaA>FMd_=H}w! z;^5%m-rnBb-QC>W+}qpR+S=OL+1c3G*w@$B*4Eb4)YQ|{)zHw=&d$%x&CScp%gV~i z$;rvc$jHXV#>B+L!^6YE!otD9!N9=4zrVk|y}i7=yt})*y1Kf#xw*Hux3;#nwY9ah zw6wFcv$C?Xv9YnRu&}SMudc4Ht*x!BtgNf6tE#H1si~={sjjD|r>3T+rKP2$q@<&x zqobp!qN1Xqp`oFnrJ$goprE6lpP!zdp`MSWoSd7Ro12@UnwpxLnw^=MnV6WE zmzS58mX?*3mz9;3mX?*2l$4W`lai8@l9G~eg|M^H&l zLpBo?51@vfgB2q_TVh*dNP<;cR$Wg!vYsMHR!qvvOis>GNH`+ zJ3B|tqgANiBSy@x>Q#;x7+DuU7&rwlf#S04)VZvA$XoUy8Y&f7)SqP<}Lw@L# zA(@Cohl`6CZyedUu^BlmK|DG5$Kl2f8z@uCc)^k-3m7$G!njf7$;XhOW>^`rV#UFh zEN#eG;bP?tCs>{+)q)ceg9$aDAaTZ{MGK5rU8ty$qz8){MT#gHGX{#XEJHLonBXFa zj+#9GE&^pq!`qG`K5iiC!gq}sRY|1yD8?j++_^oR0g+)NNtZN`)08!0q=}AA4HhIo zFaa9NYu8%97=oos5f?O`lwre~4VfoIei+FyK|urxj@C(-q(sS(!$5uL3j&jg7&XY% zlr17;3GGL;2K8>CB87G97;W(2VZ((D+3Hz;L;bylfhf(kFNV8at)h;hdM z85WX(#*Hq@@BYePt3t_l{ zCL3|YVWydA0Fz{rTl65n00)c^)^-jJn1c zRVXtA6mkUMEDLU|v7{JK&_IJ2ciiCy7BOT1fdUBh8b=yrbYaCAchCU_7?H`b1`}4q zLB|_mI2!;7W4QCq6F1O+MW||6AwmKafUrReUA&QotxQZI8D$G)AuSVV@X<&A9v;~H zKnWjo&;bljq=29aCeV-t5GBYkL=Q}q(S~FLd2t39MyRmC%_GFHkPc7CfIt8P*emqV z0YK2j9A+kmW^!tn(ZmG+L=6DZR99W}8p9?Utr=#t@rE2=zxf3QQ(JBJ&<{Z2>8EUP zeX1B)2w_3gXV)D-0Tt+=#@cV-0f!PU#MglZ3m6b}0e08zK^x;9(u?Tga{%?&nNTXhcEuM_#J>yL>p*a zuZJ2pliCGSp!Ye8>YFq@)ZOW-uT~OrjFQK!)UyVGFt7ni'); + }, + + init : function(ed) { + var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, dom = ed.dom, n = ed.selection.getNode(); + + tinyMCEPopup.resizeToInnerSize(); + this.fillClassList('class_list'); + this.fillFileList('src_list', 'tinyMCEImageList'); + this.fillFileList('over_list', 'tinyMCEImageList'); + this.fillFileList('out_list', 'tinyMCEImageList'); + TinyMCE_EditableSelects.init(); + + if (n.nodeName == 'IMG') { + nl.src.value = dom.getAttrib(n, 'src'); + nl.width.value = dom.getAttrib(n, 'width'); + nl.height.value = dom.getAttrib(n, 'height'); + nl.alt.value = dom.getAttrib(n, 'alt'); + nl.title.value = dom.getAttrib(n, 'title'); + nl.vspace.value = this.getAttrib(n, 'vspace'); + nl.hspace.value = this.getAttrib(n, 'hspace'); + nl.border.value = this.getAttrib(n, 'border'); + selectByValue(f, 'align', this.getAttrib(n, 'align')); + selectByValue(f, 'class_list', dom.getAttrib(n, 'class'), true, true); + nl.style.value = dom.getAttrib(n, 'style'); + nl.id.value = dom.getAttrib(n, 'id'); + nl.dir.value = dom.getAttrib(n, 'dir'); + nl.lang.value = dom.getAttrib(n, 'lang'); + nl.usemap.value = dom.getAttrib(n, 'usemap'); + nl.longdesc.value = dom.getAttrib(n, 'longdesc'); + nl.insert.value = ed.getLang('update'); + + if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseover'))) + nl.onmouseoversrc.value = dom.getAttrib(n, 'onmouseover').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); + + if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseout'))) + nl.onmouseoutsrc.value = dom.getAttrib(n, 'onmouseout').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); + + if (ed.settings.inline_styles) { + // Move attribs to styles + if (dom.getAttrib(n, 'align')) + this.updateStyle('align'); + + if (dom.getAttrib(n, 'hspace')) + this.updateStyle('hspace'); + + if (dom.getAttrib(n, 'border')) + this.updateStyle('border'); + + if (dom.getAttrib(n, 'vspace')) + this.updateStyle('vspace'); + } + } + + // Setup browse button + document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); + if (isVisible('srcbrowser')) + document.getElementById('src').style.width = '260px'; + + // Setup browse button + document.getElementById('onmouseoversrccontainer').innerHTML = getBrowserHTML('overbrowser','onmouseoversrc','image','theme_advanced_image'); + if (isVisible('overbrowser')) + document.getElementById('onmouseoversrc').style.width = '260px'; + + // Setup browse button + document.getElementById('onmouseoutsrccontainer').innerHTML = getBrowserHTML('outbrowser','onmouseoutsrc','image','theme_advanced_image'); + if (isVisible('outbrowser')) + document.getElementById('onmouseoutsrc').style.width = '260px'; + + // If option enabled default contrain proportions to checked + if (ed.getParam("advimage_constrain_proportions", true)) + f.constrain.checked = true; + + // Check swap image if valid data + if (nl.onmouseoversrc.value || nl.onmouseoutsrc.value) + this.setSwapImage(true); + else + this.setSwapImage(false); + + this.changeAppearance(); + this.showPreviewImage(nl.src.value, 1); + }, + + insert : function(file, title) { + var ed = tinyMCEPopup.editor, t = this, f = document.forms[0]; + + if (f.src.value === '') { + if (ed.selection.getNode().nodeName == 'IMG') { + ed.dom.remove(ed.selection.getNode()); + ed.execCommand('mceRepaint'); + } + + tinyMCEPopup.close(); + return; + } + + if (tinyMCEPopup.getParam("accessibility_warnings", 1)) { + if (!f.alt.value) { + tinyMCEPopup.confirm(tinyMCEPopup.getLang('advimage_dlg.missing_alt'), function(s) { + if (s) + t.insertAndClose(); + }); + + return; + } + } + + t.insertAndClose(); + }, + + insertAndClose : function() { + var ed = tinyMCEPopup.editor, f = document.forms[0], nl = f.elements, v, args = {}, el; + + tinyMCEPopup.restoreSelection(); + + // Fixes crash in Safari + if (tinymce.isWebKit) + ed.getWin().focus(); + + if (!ed.settings.inline_styles) { + args = { + vspace : nl.vspace.value, + hspace : nl.hspace.value, + border : nl.border.value, + align : getSelectValue(f, 'align') + }; + } else { + // Remove deprecated values + args = { + vspace : '', + hspace : '', + border : '', + align : '' + }; + } + + tinymce.extend(args, { + src : nl.src.value, + width : nl.width.value, + height : nl.height.value, + alt : nl.alt.value, + title : nl.title.value, + 'class' : getSelectValue(f, 'class_list'), + style : nl.style.value, + id : nl.id.value, + dir : nl.dir.value, + lang : nl.lang.value, + usemap : nl.usemap.value, + longdesc : nl.longdesc.value + }); + + args.onmouseover = args.onmouseout = ''; + + if (f.onmousemovecheck.checked) { + if (nl.onmouseoversrc.value) + args.onmouseover = "this.src='" + nl.onmouseoversrc.value + "';"; + + if (nl.onmouseoutsrc.value) + args.onmouseout = "this.src='" + nl.onmouseoutsrc.value + "';"; + } + + el = ed.selection.getNode(); + + if (el && el.nodeName == 'IMG') { + ed.dom.setAttribs(el, args); + } else { + ed.execCommand('mceInsertContent', false, '', {skip_undo : 1}); + ed.dom.setAttribs('__mce_tmp', args); + ed.dom.setAttrib('__mce_tmp', 'id', ''); + ed.undoManager.add(); + } + + tinyMCEPopup.close(); + }, + + getAttrib : function(e, at) { + var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; + + if (ed.settings.inline_styles) { + switch (at) { + case 'align': + if (v = dom.getStyle(e, 'float')) + return v; + + if (v = dom.getStyle(e, 'vertical-align')) + return v; + + break; + + case 'hspace': + v = dom.getStyle(e, 'margin-left') + v2 = dom.getStyle(e, 'margin-right'); + + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'vspace': + v = dom.getStyle(e, 'margin-top') + v2 = dom.getStyle(e, 'margin-bottom'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'border': + v = 0; + + tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { + sv = dom.getStyle(e, 'border-' + sv + '-width'); + + // False or not the same as prev + if (!sv || (sv != v && v !== 0)) { + v = 0; + return false; + } + + if (sv) + v = sv; + }); + + if (v) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + } + } + + if (v = dom.getAttrib(e, at)) + return v; + + return ''; + }, + + setSwapImage : function(st) { + var f = document.forms[0]; + + f.onmousemovecheck.checked = st; + setBrowserDisabled('overbrowser', !st); + setBrowserDisabled('outbrowser', !st); + + if (f.over_list) + f.over_list.disabled = !st; + + if (f.out_list) + f.out_list.disabled = !st; + + f.onmouseoversrc.disabled = !st; + f.onmouseoutsrc.disabled = !st; + }, + + fillClassList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { + cl = []; + + tinymce.each(v.split(';'), function(v) { + var p = v.split('='); + + cl.push({'title' : p[0], 'class' : p[1]}); + }); + } else + cl = tinyMCEPopup.editor.dom.getClasses(); + + if (cl.length > 0) { + lst.options.length = 0; + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + + tinymce.each(cl, function(o) { + lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = window[l]; + lst.options.length = 0; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + resetImageData : function() { + var f = document.forms[0]; + + f.elements.width.value = f.elements.height.value = ''; + }, + + updateImageData : function(img, st) { + var f = document.forms[0]; + + if (!st) { + f.elements.width.value = img.width; + f.elements.height.value = img.height; + } + + this.preloadImg = img; + }, + + changeAppearance : function() { + var ed = tinyMCEPopup.editor, f = document.forms[0], img = document.getElementById('alignSampleImg'); + + if (img) { + if (ed.getParam('inline_styles')) { + ed.dom.setAttrib(img, 'style', f.style.value); + } else { + img.align = f.align.value; + img.border = f.border.value; + img.hspace = f.hspace.value; + img.vspace = f.vspace.value; + } + } + }, + + changeHeight : function() { + var f = document.forms[0], tp, t = this; + + if (!f.constrain.checked || !t.preloadImg) { + return; + } + + if (f.width.value == "" || f.height.value == "") + return; + + tp = (parseInt(f.width.value) / parseInt(t.preloadImg.width)) * t.preloadImg.height; + f.height.value = tp.toFixed(0); + }, + + changeWidth : function() { + var f = document.forms[0], tp, t = this; + + if (!f.constrain.checked || !t.preloadImg) { + return; + } + + if (f.width.value == "" || f.height.value == "") + return; + + tp = (parseInt(f.height.value) / parseInt(t.preloadImg.height)) * t.preloadImg.width; + f.width.value = tp.toFixed(0); + }, + + updateStyle : function(ty) { + var dom = tinyMCEPopup.dom, st, v, f = document.forms[0], img = dom.create('img', {style : dom.get('style').value}); + + if (tinyMCEPopup.editor.settings.inline_styles) { + // Handle align + if (ty == 'align') { + dom.setStyle(img, 'float', ''); + dom.setStyle(img, 'vertical-align', ''); + + v = getSelectValue(f, 'align'); + if (v) { + if (v == 'left' || v == 'right') + dom.setStyle(img, 'float', v); + else + img.style.verticalAlign = v; + } + } + + // Handle border + if (ty == 'border') { + dom.setStyle(img, 'border', ''); + + v = f.border.value; + if (v || v == '0') { + if (v == '0') + img.style.border = '0'; + else + img.style.border = v + 'px solid black'; + } + } + + // Handle hspace + if (ty == 'hspace') { + dom.setStyle(img, 'marginLeft', ''); + dom.setStyle(img, 'marginRight', ''); + + v = f.hspace.value; + if (v) { + img.style.marginLeft = v + 'px'; + img.style.marginRight = v + 'px'; + } + } + + // Handle vspace + if (ty == 'vspace') { + dom.setStyle(img, 'marginTop', ''); + dom.setStyle(img, 'marginBottom', ''); + + v = f.vspace.value; + if (v) { + img.style.marginTop = v + 'px'; + img.style.marginBottom = v + 'px'; + } + } + + // Merge + dom.get('style').value = dom.serializeStyle(dom.parseStyle(img.style.cssText), 'img'); + } + }, + + changeMouseMove : function() { + }, + + showPreviewImage : function(u, st) { + if (!u) { + tinyMCEPopup.dom.setHTML('prev', ''); + return; + } + + if (!st && tinyMCEPopup.getParam("advimage_update_dimensions_onchange", true)) + this.resetImageData(); + + u = tinyMCEPopup.editor.documentBaseURI.toAbsolute(u); + + if (!st) + tinyMCEPopup.dom.setHTML('prev', ''); + else + tinyMCEPopup.dom.setHTML('prev', ''); + } +}; + +ImageDialog.preInit(); +tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); diff --git a/web/js/globals/tinymce/plugins/advimage/langs/en_dlg.js b/web/js/globals/tinymce/plugins/advimage/langs/en_dlg.js new file mode 100644 index 0000000..f493d19 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advimage/langs/en_dlg.js @@ -0,0 +1,43 @@ +tinyMCE.addI18n('en.advimage_dlg',{ +tab_general:"General", +tab_appearance:"Appearance", +tab_advanced:"Advanced", +general:"General", +title:"Title", +preview:"Preview", +constrain_proportions:"Constrain proportions", +langdir:"Language direction", +langcode:"Language code", +long_desc:"Long description link", +style:"Style", +classes:"Classes", +ltr:"Left to right", +rtl:"Right to left", +id:"Id", +map:"Image map", +swap_image:"Swap image", +alt_image:"Alternative image", +mouseover:"for mouse over", +mouseout:"for mouse out", +misc:"Miscellaneous", +example_img:"Appearance preview image", +missing_alt:"Are you sure you want to continue without including an Image Description? Without it the image may not be accessible to some users with disabilities, or to those using a text browser, or browsing the Web with images turned off.", +dialog_title:"Insert/edit image", +src:"Image URL", +alt:"Image description", +list:"Image list", +border:"Border", +dimensions:"Dimensions", +vspace:"Vertical space", +hspace:"Horizontal space", +align:"Alignment", +align_baseline:"Baseline", +align_top:"Top", +align_middle:"Middle", +align_bottom:"Bottom", +align_texttop:"Text top", +align_textbottom:"Text bottom", +align_left:"Left", +align_right:"Right", +image_list:"Image list" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advlink/css/advlink.css b/web/js/globals/tinymce/plugins/advlink/css/advlink.css new file mode 100644 index 0000000..1436431 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlink/css/advlink.css @@ -0,0 +1,8 @@ +.mceLinkList, .mceAnchorList, #targetlist {width:280px;} +.mceActionPanel {margin-top:7px;} +.panel_wrapper div.current {height:320px;} +#classlist, #title, #href {width:280px;} +#popupurl, #popupname {width:200px;} +#popupwidth, #popupheight, #popupleft, #popuptop {width:30px;vertical-align:middle;text-align:center;} +#id, #style, #classes, #target, #dir, #hreflang, #lang, #charset, #type, #rel, #rev, #tabindex, #accesskey {width:200px;} +#events_panel input {width:200px;} diff --git a/web/js/globals/tinymce/plugins/advlink/editor_plugin.js b/web/js/globals/tinymce/plugins/advlink/editor_plugin.js new file mode 100644 index 0000000..983fe5a --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlink/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.AdvancedLinkPlugin",{init:function(a,b){this.editor=a;a.addCommand("mceAdvLink",function(){var c=a.selection;if(c.isCollapsed()&&!a.dom.getParent(c.getNode(),"A")){return}a.windowManager.open({file:b+"/link.htm",width:480+parseInt(a.getLang("advlink.delta_width",0)),height:400+parseInt(a.getLang("advlink.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("link",{title:"advlink.link_desc",cmd:"mceAdvLink"});a.addShortcut("ctrl+k","advlink.advlink_desc","mceAdvLink");a.onNodeChange.add(function(d,c,f,e){c.setDisabled("link",e&&f.nodeName!="A");c.setActive("link",f.nodeName=="A"&&!f.name)})},getInfo:function(){return{longname:"Advanced link",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advlink",tinymce.plugins.AdvancedLinkPlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advlink/editor_plugin_src.js b/web/js/globals/tinymce/plugins/advlink/editor_plugin_src.js new file mode 100644 index 0000000..14e46a7 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlink/editor_plugin_src.js @@ -0,0 +1,61 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.AdvancedLinkPlugin', { + init : function(ed, url) { + this.editor = ed; + + // Register commands + ed.addCommand('mceAdvLink', function() { + var se = ed.selection; + + // No selection and not in link + if (se.isCollapsed() && !ed.dom.getParent(se.getNode(), 'A')) + return; + + ed.windowManager.open({ + file : url + '/link.htm', + width : 480 + parseInt(ed.getLang('advlink.delta_width', 0)), + height : 400 + parseInt(ed.getLang('advlink.delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register buttons + ed.addButton('link', { + title : 'advlink.link_desc', + cmd : 'mceAdvLink' + }); + + ed.addShortcut('ctrl+k', 'advlink.advlink_desc', 'mceAdvLink'); + + ed.onNodeChange.add(function(ed, cm, n, co) { + cm.setDisabled('link', co && n.nodeName != 'A'); + cm.setActive('link', n.nodeName == 'A' && !n.name); + }); + }, + + getInfo : function() { + return { + longname : 'Advanced link', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('advlink', tinymce.plugins.AdvancedLinkPlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advlink/js/advlink.js b/web/js/globals/tinymce/plugins/advlink/js/advlink.js new file mode 100644 index 0000000..b78e82f --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlink/js/advlink.js @@ -0,0 +1,528 @@ +/* Functions for the advlink plugin popup */ + +tinyMCEPopup.requireLangPack(); + +var templates = { + "window.open" : "window.open('${url}','${target}','${options}')" +}; + +function preinit() { + var url; + + if (url = tinyMCEPopup.getParam("external_link_list_url")) + document.write(''); +} + +function changeClass() { + var f = document.forms[0]; + + f.classes.value = getSelectValue(f, 'classlist'); +} + +function init() { + tinyMCEPopup.resizeToInnerSize(); + + var formObj = document.forms[0]; + var inst = tinyMCEPopup.editor; + var elm = inst.selection.getNode(); + var action = "insert"; + var html; + + document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser','href','file','advlink'); + document.getElementById('popupurlbrowsercontainer').innerHTML = getBrowserHTML('popupurlbrowser','popupurl','file','advlink'); + document.getElementById('linklisthrefcontainer').innerHTML = getLinkListHTML('linklisthref','href'); + document.getElementById('anchorlistcontainer').innerHTML = getAnchorListHTML('anchorlist','href'); + document.getElementById('targetlistcontainer').innerHTML = getTargetListHTML('targetlist','target'); + + // Link list + html = getLinkListHTML('linklisthref','href'); + if (html == "") + document.getElementById("linklisthrefrow").style.display = 'none'; + else + document.getElementById("linklisthrefcontainer").innerHTML = html; + + // Resize some elements + if (isVisible('hrefbrowser')) + document.getElementById('href').style.width = '260px'; + + if (isVisible('popupurlbrowser')) + document.getElementById('popupurl').style.width = '180px'; + + elm = inst.dom.getParent(elm, "A"); + if (elm != null && elm.nodeName == "A") + action = "update"; + + formObj.insert.value = tinyMCEPopup.getLang(action, 'Insert', true); + + setPopupControlsDisabled(true); + + if (action == "update") { + var href = inst.dom.getAttrib(elm, 'href'); + var onclick = inst.dom.getAttrib(elm, 'onclick'); + + // Setup form data + setFormValue('href', href); + setFormValue('title', inst.dom.getAttrib(elm, 'title')); + setFormValue('id', inst.dom.getAttrib(elm, 'id')); + setFormValue('style', inst.dom.getAttrib(elm, "style")); + setFormValue('rel', inst.dom.getAttrib(elm, 'rel')); + setFormValue('rev', inst.dom.getAttrib(elm, 'rev')); + setFormValue('charset', inst.dom.getAttrib(elm, 'charset')); + setFormValue('hreflang', inst.dom.getAttrib(elm, 'hreflang')); + setFormValue('dir', inst.dom.getAttrib(elm, 'dir')); + setFormValue('lang', inst.dom.getAttrib(elm, 'lang')); + setFormValue('tabindex', inst.dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); + setFormValue('accesskey', inst.dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); + setFormValue('type', inst.dom.getAttrib(elm, 'type')); + setFormValue('onfocus', inst.dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', inst.dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', onclick); + setFormValue('ondblclick', inst.dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', inst.dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', inst.dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', inst.dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', inst.dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', inst.dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', inst.dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', inst.dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', inst.dom.getAttrib(elm, 'onkeyup')); + setFormValue('target', inst.dom.getAttrib(elm, 'target')); + setFormValue('classes', inst.dom.getAttrib(elm, 'class')); + + // Parse onclick data + if (onclick != null && onclick.indexOf('window.open') != -1) + parseWindowOpen(onclick); + else + parseFunction(onclick); + + // Select by the values + selectByValue(formObj, 'dir', inst.dom.getAttrib(elm, 'dir')); + selectByValue(formObj, 'rel', inst.dom.getAttrib(elm, 'rel')); + selectByValue(formObj, 'rev', inst.dom.getAttrib(elm, 'rev')); + selectByValue(formObj, 'linklisthref', href); + + if (href.charAt(0) == '#') + selectByValue(formObj, 'anchorlist', href); + + addClassesToList('classlist', 'advlink_styles'); + + selectByValue(formObj, 'classlist', inst.dom.getAttrib(elm, 'class'), true); + selectByValue(formObj, 'targetlist', inst.dom.getAttrib(elm, 'target'), true); + } else + addClassesToList('classlist', 'advlink_styles'); +} + +function checkPrefix(n) { + if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_email'))) + n.value = 'mailto:' + n.value; + + if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_external'))) + n.value = 'http://' + n.value; +} + +function setFormValue(name, value) { + document.forms[0].elements[name].value = value; +} + +function parseWindowOpen(onclick) { + var formObj = document.forms[0]; + + // Preprocess center code + if (onclick.indexOf('return false;') != -1) { + formObj.popupreturn.checked = true; + onclick = onclick.replace('return false;', ''); + } else + formObj.popupreturn.checked = false; + + var onClickData = parseLink(onclick); + + if (onClickData != null) { + formObj.ispopup.checked = true; + setPopupControlsDisabled(false); + + var onClickWindowOptions = parseOptions(onClickData['options']); + var url = onClickData['url']; + + formObj.popupname.value = onClickData['target']; + formObj.popupurl.value = url; + formObj.popupwidth.value = getOption(onClickWindowOptions, 'width'); + formObj.popupheight.value = getOption(onClickWindowOptions, 'height'); + + formObj.popupleft.value = getOption(onClickWindowOptions, 'left'); + formObj.popuptop.value = getOption(onClickWindowOptions, 'top'); + + if (formObj.popupleft.value.indexOf('screen') != -1) + formObj.popupleft.value = "c"; + + if (formObj.popuptop.value.indexOf('screen') != -1) + formObj.popuptop.value = "c"; + + formObj.popuplocation.checked = getOption(onClickWindowOptions, 'location') == "yes"; + formObj.popupscrollbars.checked = getOption(onClickWindowOptions, 'scrollbars') == "yes"; + formObj.popupmenubar.checked = getOption(onClickWindowOptions, 'menubar') == "yes"; + formObj.popupresizable.checked = getOption(onClickWindowOptions, 'resizable') == "yes"; + formObj.popuptoolbar.checked = getOption(onClickWindowOptions, 'toolbar') == "yes"; + formObj.popupstatus.checked = getOption(onClickWindowOptions, 'status') == "yes"; + formObj.popupdependent.checked = getOption(onClickWindowOptions, 'dependent') == "yes"; + + buildOnClick(); + } +} + +function parseFunction(onclick) { + var formObj = document.forms[0]; + var onClickData = parseLink(onclick); + + // TODO: Add stuff here +} + +function getOption(opts, name) { + return typeof(opts[name]) == "undefined" ? "" : opts[name]; +} + +function setPopupControlsDisabled(state) { + var formObj = document.forms[0]; + + formObj.popupname.disabled = state; + formObj.popupurl.disabled = state; + formObj.popupwidth.disabled = state; + formObj.popupheight.disabled = state; + formObj.popupleft.disabled = state; + formObj.popuptop.disabled = state; + formObj.popuplocation.disabled = state; + formObj.popupscrollbars.disabled = state; + formObj.popupmenubar.disabled = state; + formObj.popupresizable.disabled = state; + formObj.popuptoolbar.disabled = state; + formObj.popupstatus.disabled = state; + formObj.popupreturn.disabled = state; + formObj.popupdependent.disabled = state; + + setBrowserDisabled('popupurlbrowser', state); +} + +function parseLink(link) { + link = link.replace(new RegExp(''', 'g'), "'"); + + var fnName = link.replace(new RegExp("\\s*([A-Za-z0-9\.]*)\\s*\\(.*", "gi"), "$1"); + + // Is function name a template function + var template = templates[fnName]; + if (template) { + // Build regexp + var variableNames = template.match(new RegExp("'?\\$\\{[A-Za-z0-9\.]*\\}'?", "gi")); + var regExp = "\\s*[A-Za-z0-9\.]*\\s*\\("; + var replaceStr = ""; + for (var i=0; i'); + for (var i=0; i'; + html += ''; + + for (i=0; i' + name + ''; + } + + html += ''; + + return html; +} + +function insertAction() { + var inst = tinyMCEPopup.editor; + var elm, elementArray, i; + + elm = inst.selection.getNode(); + checkPrefix(document.forms[0].href); + + elm = inst.dom.getParent(elm, "A"); + + // Remove element if there is no href + if (!document.forms[0].href.value) { + tinyMCEPopup.execCommand("mceBeginUndoLevel"); + i = inst.selection.getBookmark(); + inst.dom.remove(elm, 1); + inst.selection.moveToBookmark(i); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + return; + } + + tinyMCEPopup.execCommand("mceBeginUndoLevel"); + + // Create new anchor elements + if (elm == null) { + inst.getDoc().execCommand("unlink", false, null); + tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1}); + + elementArray = tinymce.grep(inst.dom.select("a"), function(n) {return inst.dom.getAttrib(n, 'href') == '#mce_temp_url#';}); + for (i=0; i' + tinyMCELinkList[i][0] + ''; + + html += ''; + + return html; + + // tinyMCE.debug('-- image list start --', html, '-- image list end --'); +} + +function getTargetListHTML(elm_id, target_form_element) { + var targets = tinyMCEPopup.getParam('theme_advanced_link_targets', '').split(';'); + var html = ''; + + html += ''; + + return html; +} + +// While loading +preinit(); +tinyMCEPopup.onInit.add(init); diff --git a/web/js/globals/tinymce/plugins/advlink/langs/en_dlg.js b/web/js/globals/tinymce/plugins/advlink/langs/en_dlg.js new file mode 100644 index 0000000..c71ffbd --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlink/langs/en_dlg.js @@ -0,0 +1,52 @@ +tinyMCE.addI18n('en.advlink_dlg',{ +title:"Insert/edit link", +url:"Link URL", +target:"Target", +titlefield:"Title", +is_email:"The URL you entered seems to be an email address, do you want to add the required mailto: prefix?", +is_external:"The URL you entered seems to external link, do you want to add the required http:// prefix?", +list:"Link list", +general_tab:"General", +popup_tab:"Popup", +events_tab:"Events", +advanced_tab:"Advanced", +general_props:"General properties", +popup_props:"Popup properties", +event_props:"Events", +advanced_props:"Advanced properties", +popup_opts:"Options", +anchor_names:"Anchors", +target_same:"Open in this window / frame", +target_parent:"Open in parent window / frame", +target_top:"Open in top frame (replaces all frames)", +target_blank:"Open in new window", +popup:"Javascript popup", +popup_url:"Popup URL", +popup_name:"Window name", +popup_return:"Insert 'return false'", +popup_scrollbars:"Show scrollbars", +popup_statusbar:"Show status bar", +popup_toolbar:"Show toolbars", +popup_menubar:"Show menu bar", +popup_location:"Show location bar", +popup_resizable:"Make window resizable", +popup_dependent:"Dependent (Mozilla/Firefox only)", +popup_size:"Size", +popup_position:"Position (X/Y)", +id:"Id", +style:"Style", +classes:"Classes", +target_name:"Target name", +langdir:"Language direction", +target_langcode:"Target language", +langcode:"Language code", +encoding:"Target character encoding", +mime:"Target MIME type", +rel:"Relationship page to target", +rev:"Relationship target to page", +tabindex:"Tabindex", +accesskey:"Accesskey", +ltr:"Left to right", +rtl:"Right to left", +link_list:"Link list" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advlink/link.htm b/web/js/globals/tinymce/plugins/advlink/link.htm new file mode 100644 index 0000000..876669c --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlink/link.htm @@ -0,0 +1,333 @@ + + + + {#advlink_dlg.title} + + + + + + + + +
      + + +
      +
      +
      + {#advlink_dlg.general_props} + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
       
      + +
      +
      +
      + + + +
      +
      + {#advlink_dlg.advanced_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + +
      +
      +
      +
      +
      + +
      +
      + {#advlink_dlg.event_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/advlist/editor_plugin.js b/web/js/globals/tinymce/plugins/advlist/editor_plugin.js new file mode 100644 index 0000000..02d1697 --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlist/editor_plugin.js @@ -0,0 +1 @@ +(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.AdvListPlugin",{init:function(b,c){var d=this;d.editor=b;function e(g){var f=[];a(g.split(/,/),function(h){f.push({title:"advlist."+(h=="default"?"def":h.replace(/-/g,"_")),styles:{listStyleType:h=="default"?"":h}})});return f}d.numlist=b.getParam("advlist_number_styles")||e("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman");d.bullist=b.getParam("advlist_bullet_styles")||e("default,circle,disc,square")},createControl:function(d,b){var f=this,e,h;if(d=="numlist"||d=="bullist"){if(f[d][0].title=="advlist.def"){h=f[d][0]}function c(i,k){var j=true;a(k.styles,function(m,l){if(f.editor.dom.getStyle(i,l)!=m){j=false;return false}});return j}function g(){var k,i=f.editor,l=i.dom,j=i.selection;k=l.getParent(j.getNode(),"ol,ul");if(!k||k.nodeName==(d=="bullist"?"OL":"UL")||c(k,h)){i.execCommand(d=="bullist"?"InsertUnorderedList":"InsertOrderedList")}if(h){k=l.getParent(j.getNode(),"ol,ul");if(k){l.setStyles(k,h.styles);k.removeAttribute("_mce_style")}}}e=b.createSplitButton(d,{title:"advanced."+d+"_desc","class":"mce_"+d,onclick:function(){g()}});e.onRenderMenu.add(function(i,j){j.onShowMenu.add(function(){var m=f.editor.dom,l=m.getParent(f.editor.selection.getNode(),"ol,ul"),k;if(l||h){k=f[d];a(j.items,function(n){var o=true;n.setSelected(0);if(l&&!n.isDisabled()){a(k,function(p){if(p.id==n.id){if(!c(l,p)){o=false;return false}}});if(o){n.setSelected(1)}}});if(!l){j.items[h.id].setSelected(1)}}});j.add({id:f.editor.dom.uniqueId(),title:"advlist.types","class":"mceMenuItemTitle"}).setDisabled(1);a(f[d],function(k){k.id=f.editor.dom.uniqueId();j.add({id:k.id,title:k.title,onclick:function(){h=k;g()}})})});return e}},getInfo:function(){return{longname:"Advanced lists",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advlist",tinymce.plugins.AdvListPlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/advlist/editor_plugin_src.js b/web/js/globals/tinymce/plugins/advlist/editor_plugin_src.js new file mode 100644 index 0000000..a61887a --- /dev/null +++ b/web/js/globals/tinymce/plugins/advlist/editor_plugin_src.js @@ -0,0 +1,154 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each; + + tinymce.create('tinymce.plugins.AdvListPlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + function buildFormats(str) { + var formats = []; + + each(str.split(/,/), function(type) { + formats.push({ + title : 'advlist.' + (type == 'default' ? 'def' : type.replace(/-/g, '_')), + styles : { + listStyleType : type == 'default' ? '' : type + } + }); + }); + + return formats; + }; + + // Setup number formats from config or default + t.numlist = ed.getParam("advlist_number_styles") || buildFormats("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman"); + t.bullist = ed.getParam("advlist_bullet_styles") || buildFormats("default,circle,disc,square"); + }, + + createControl: function(name, cm) { + var t = this, btn, format; + + if (name == 'numlist' || name == 'bullist') { + // Default to first item if it's a default item + if (t[name][0].title == 'advlist.def') + format = t[name][0]; + + function hasFormat(node, format) { + var state = true; + + each(format.styles, function(value, name) { + // Format doesn't match + if (t.editor.dom.getStyle(node, name) != value) { + state = false; + return false; + } + }); + + return state; + }; + + function applyListFormat() { + var list, ed = t.editor, dom = ed.dom, sel = ed.selection; + + // Check for existing list element + list = dom.getParent(sel.getNode(), 'ol,ul'); + + // Switch/add list type if needed + if (!list || list.nodeName == (name == 'bullist' ? 'OL' : 'UL') || hasFormat(list, format)) + ed.execCommand(name == 'bullist' ? 'InsertUnorderedList' : 'InsertOrderedList'); + + // Append styles to new list element + if (format) { + list = dom.getParent(sel.getNode(), 'ol,ul'); + + if (list) { + dom.setStyles(list, format.styles); + list.removeAttribute('_mce_style'); + } + } + }; + + btn = cm.createSplitButton(name, { + title : 'advanced.' + name + '_desc', + 'class' : 'mce_' + name, + onclick : function() { + applyListFormat(); + } + }); + + btn.onRenderMenu.add(function(btn, menu) { + menu.onShowMenu.add(function() { + var dom = t.editor.dom, list = dom.getParent(t.editor.selection.getNode(), 'ol,ul'), fmtList; + + if (list || format) { + fmtList = t[name]; + + // Unselect existing items + each(menu.items, function(item) { + var state = true; + + item.setSelected(0); + + if (list && !item.isDisabled()) { + each(fmtList, function(fmt) { + if (fmt.id == item.id) { + if (!hasFormat(list, fmt)) { + state = false; + return false; + } + } + }); + + if (state) + item.setSelected(1); + } + }); + + // Select the current format + if (!list) + menu.items[format.id].setSelected(1); + } + }); + + menu.add({id : t.editor.dom.uniqueId(), title : 'advlist.types', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + + each(t[name], function(item) { + item.id = t.editor.dom.uniqueId(); + + menu.add({id : item.id, title : item.title, onclick : function() { + format = item; + applyListFormat(); + }}); + }); + }); + + return btn; + } + }, + + getInfo : function() { + return { + longname : 'Advanced lists', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('advlist', tinymce.plugins.AdvListPlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/autoresize/editor_plugin.js b/web/js/globals/tinymce/plugins/autoresize/editor_plugin.js new file mode 100644 index 0000000..1676b15 --- /dev/null +++ b/web/js/globals/tinymce/plugins/autoresize/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.AutoResizePlugin",{init:function(a,c){var d=this;if(a.getParam("fullscreen_is_enabled")){return}function b(){var h=a.getDoc(),e=h.body,j=h.documentElement,g=tinymce.DOM,i=d.autoresize_min_height,f;f=tinymce.isIE?e.scrollHeight:j.offsetHeight;if(f>d.autoresize_min_height){i=f}g.setStyle(g.get(a.id+"_ifr"),"height",i+"px");if(d.throbbing){a.setProgressState(false);a.setProgressState(true)}}d.editor=a;d.autoresize_min_height=a.getElement().offsetHeight;a.onChange.add(b);a.onSetContent.add(b);a.onPaste.add(b);a.onKeyUp.add(b);a.onPostRender.add(b);if(a.getParam("autoresize_on_init",true)){a.onInit.add(function(f,e){f.setProgressState(true);d.throbbing=true;f.getBody().style.overflowY="hidden"});a.onLoadContent.add(function(f,e){b();setTimeout(function(){b();f.setProgressState(false);d.throbbing=false},1250)})}a.addCommand("mceAutoResize",b)},getInfo:function(){return{longname:"Auto Resize",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autoresize",tinymce.plugins.AutoResizePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/autoresize/editor_plugin_src.js b/web/js/globals/tinymce/plugins/autoresize/editor_plugin_src.js new file mode 100644 index 0000000..c260b7a --- /dev/null +++ b/web/js/globals/tinymce/plugins/autoresize/editor_plugin_src.js @@ -0,0 +1,119 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + /** + * Auto Resize + * + * This plugin automatically resizes the content area to fit its content height. + * It will retain a minimum height, which is the height of the content area when + * it's initialized. + */ + tinymce.create('tinymce.plugins.AutoResizePlugin', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed, url) { + var t = this; + + if (ed.getParam('fullscreen_is_enabled')) + return; + + /** + * This method gets executed each time the editor needs to resize. + */ + function resize() { + var d = ed.getDoc(), b = d.body, de = d.documentElement, DOM = tinymce.DOM, resizeHeight = t.autoresize_min_height, myHeight; + + // Get height differently depending on the browser used + myHeight = tinymce.isIE ? b.scrollHeight : de.offsetHeight; + + // Don't make it smaller than the minimum height + if (myHeight > t.autoresize_min_height) + resizeHeight = myHeight; + + // Resize content element + DOM.setStyle(DOM.get(ed.id + '_ifr'), 'height', resizeHeight + 'px'); + + // if we're throbbing, we'll re-throb to match the new size + if (t.throbbing) { + ed.setProgressState(false); + ed.setProgressState(true); + } + }; + + t.editor = ed; + + // Define minimum height + t.autoresize_min_height = ed.getElement().offsetHeight; + + // Add appropriate listeners for resizing content area + ed.onChange.add(resize); + ed.onSetContent.add(resize); + ed.onPaste.add(resize); + ed.onKeyUp.add(resize); + ed.onPostRender.add(resize); + + if (ed.getParam('autoresize_on_init', true)) { + // Things to do when the editor is ready + ed.onInit.add(function(ed, l) { + // Show throbber until content area is resized properly + ed.setProgressState(true); + t.throbbing = true; + + // Hide scrollbars + ed.getBody().style.overflowY = "hidden"; + }); + + ed.onLoadContent.add(function(ed, l) { + resize(); + + // Because the content area resizes when its content CSS loads, + // and we can't easily add a listener to its onload event, + // we'll just trigger a resize after a short loading period + setTimeout(function() { + resize(); + + // Disable throbber + ed.setProgressState(false); + t.throbbing = false; + }, 1250); + }); + } + + // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample'); + ed.addCommand('mceAutoResize', resize); + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Auto Resize', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('autoresize', tinymce.plugins.AutoResizePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/autosave/editor_plugin.js b/web/js/globals/tinymce/plugins/autosave/editor_plugin.js new file mode 100644 index 0000000..6e48540 --- /dev/null +++ b/web/js/globals/tinymce/plugins/autosave/editor_plugin.js @@ -0,0 +1 @@ +(function(e){var c="autosave",g="restoredraft",b=true,f,d,a=e.util.Dispatcher;e.create("tinymce.plugins.AutoSave",{init:function(i,j){var h=this,l=i.settings;h.editor=i;function k(n){var m={s:1000,m:60000};n=/^(\d+)([ms]?)$/.exec(""+n);return(n[2]?m[n[2]]:1)*parseInt(n)}e.each({ask_before_unload:b,interval:"30s",retention:"20m",minlength:50},function(n,m){m=c+"_"+m;if(l[m]===f){l[m]=n}});l.autosave_interval=k(l.autosave_interval);l.autosave_retention=k(l.autosave_retention);i.addButton(g,{title:c+".restore_content",onclick:function(){if(i.getContent({draft:true}).replace(/\s| |<\/?p[^>]*>|]*>/gi,"").length>0){i.windowManager.confirm(c+".warning_message",function(m){if(m){h.restoreDraft()}})}else{h.restoreDraft()}}});i.onNodeChange.add(function(){var m=i.controlManager;if(m.get(g)){m.setDisabled(g,!h.hasDraft())}});i.onInit.add(function(){if(i.controlManager.get(g)){h.setupStorage(i);setInterval(function(){h.storeDraft();i.nodeChanged()},l.autosave_interval)}});h.onStoreDraft=new a(h);h.onRestoreDraft=new a(h);h.onRemoveDraft=new a(h);if(!d){window.onbeforeunload=e.plugins.AutoSave._beforeUnloadHandler;d=b}},getInfo:function(){return{longname:"Auto save",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave",version:e.majorVersion+"."+e.minorVersion}},getExpDate:function(){return new Date(new Date().getTime()+this.editor.settings.autosave_retention).toUTCString()},setupStorage:function(i){var h=this,k=c+"_test",j="OK";h.key=c+i.id;e.each([function(){if(localStorage){localStorage.setItem(k,j);if(localStorage.getItem(k)===j){localStorage.removeItem(k);return localStorage}}},function(){if(sessionStorage){sessionStorage.setItem(k,j);if(sessionStorage.getItem(k)===j){sessionStorage.removeItem(k);return sessionStorage}}},function(){if(e.isIE){i.getElement().style.behavior="url('#default#userData')";return{autoExpires:b,setItem:function(l,n){var m=i.getElement();m.setAttribute(l,n);m.expires=h.getExpDate();m.save("TinyMCE")},getItem:function(l){var m=i.getElement();m.load("TinyMCE");return m.getAttribute(l)},removeItem:function(l){i.getElement().removeAttribute(l)}}}},],function(l){try{h.storage=l();if(h.storage){return false}}catch(m){}})},storeDraft:function(){var i=this,l=i.storage,j=i.editor,h,k;if(l){if(!l.getItem(i.key)&&!j.isDirty()){return}k=j.getContent({draft:true});if(k.length>j.settings.autosave_minlength){h=i.getExpDate();if(!i.storage.autoExpires){i.storage.setItem(i.key+"_expires",h)}i.storage.setItem(i.key,k);i.onStoreDraft.dispatch(i,{expires:h,content:k})}}},restoreDraft:function(){var h=this,i=h.storage;if(i){content=i.getItem(h.key);if(content){h.editor.setContent(content);h.onRestoreDraft.dispatch(h,{content:content})}}},hasDraft:function(){var h=this,k=h.storage,i,j;if(k){j=!!k.getItem(h.key);if(j){if(!h.storage.autoExpires){i=new Date(k.getItem(h.key+"_expires"));if(new Date().getTime()]*>|]*>/gi, "").length > 0) { + // Show confirm dialog if the editor isn't empty + ed.windowManager.confirm( + PLUGIN_NAME + ".warning_message", + function(ok) { + if (ok) + self.restoreDraft(); + } + ); + } else + self.restoreDraft(); + } + }); + + // Enable/disable restoredraft button depending on if there is a draft stored or not + ed.onNodeChange.add(function() { + var controlManager = ed.controlManager; + + if (controlManager.get(RESTORE_DRAFT)) + controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft()); + }); + + ed.onInit.add(function() { + // Check if the user added the restore button, then setup auto storage logic + if (ed.controlManager.get(RESTORE_DRAFT)) { + // Setup storage engine + self.setupStorage(ed); + + // Auto save contents each interval time + setInterval(function() { + self.storeDraft(); + ed.nodeChanged(); + }, settings.autosave_interval); + } + }); + + /** + * This event gets fired when a draft is stored to local storage. + * + * @event onStoreDraft + * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. + * @param {Object} draft Draft object containing the HTML contents of the editor. + */ + self.onStoreDraft = new Dispatcher(self); + + /** + * This event gets fired when a draft is restored from local storage. + * + * @event onStoreDraft + * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. + * @param {Object} draft Draft object containing the HTML contents of the editor. + */ + self.onRestoreDraft = new Dispatcher(self); + + /** + * This event gets fired when a draft removed/expired. + * + * @event onRemoveDraft + * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. + * @param {Object} draft Draft object containing the HTML contents of the editor. + */ + self.onRemoveDraft = new Dispatcher(self); + + // Add ask before unload dialog only add one unload handler + if (!unloadHandlerAdded) { + window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler; + unloadHandlerAdded = TRUE; + } + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @method getInfo + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Auto save', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + /** + * Returns an expiration date UTC string. + * + * @method getExpDate + * @return {String} Expiration date UTC string. + */ + getExpDate : function() { + return new Date( + new Date().getTime() + this.editor.settings.autosave_retention + ).toUTCString(); + }, + + /** + * This method will setup the storage engine. If the browser has support for it. + * + * @method setupStorage + */ + setupStorage : function(ed) { + var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK"; + + self.key = PLUGIN_NAME + ed.id; + + // Loop though each storage engine type until we find one that works + tinymce.each([ + function() { + // Try HTML5 Local Storage + if (localStorage) { + localStorage.setItem(testKey, testVal); + + if (localStorage.getItem(testKey) === testVal) { + localStorage.removeItem(testKey); + + return localStorage; + } + } + }, + + function() { + // Try HTML5 Session Storage + if (sessionStorage) { + sessionStorage.setItem(testKey, testVal); + + if (sessionStorage.getItem(testKey) === testVal) { + sessionStorage.removeItem(testKey); + + return sessionStorage; + } + } + }, + + function() { + // Try IE userData + if (tinymce.isIE) { + ed.getElement().style.behavior = "url('#default#userData')"; + + // Fake localStorage on old IE + return { + autoExpires : TRUE, + + setItem : function(key, value) { + var userDataElement = ed.getElement(); + + userDataElement.setAttribute(key, value); + userDataElement.expires = self.getExpDate(); + userDataElement.save("TinyMCE"); + }, + + getItem : function(key) { + var userDataElement = ed.getElement(); + + userDataElement.load("TinyMCE"); + + return userDataElement.getAttribute(key); + }, + + removeItem : function(key) { + ed.getElement().removeAttribute(key); + } + }; + } + }, + ], function(setup) { + // Try executing each function to find a suitable storage engine + try { + self.storage = setup(); + + if (self.storage) + return false; + } catch (e) { + // Ignore + } + }); + }, + + /** + * This method will store the current contents in the the storage engine. + * + * @method storeDraft + */ + storeDraft : function() { + var self = this, storage = self.storage, editor = self.editor, expires, content; + + // Is the contents dirty + if (storage) { + // If there is no existing key and the contents hasn't been changed since + // it's original value then there is no point in saving a draft + if (!storage.getItem(self.key) && !editor.isDirty()) + return; + + // Store contents if the contents if longer than the minlength of characters + content = editor.getContent({draft: true}); + if (content.length > editor.settings.autosave_minlength) { + expires = self.getExpDate(); + + // Store expiration date if needed IE userData has auto expire built in + if (!self.storage.autoExpires) + self.storage.setItem(self.key + "_expires", expires); + + self.storage.setItem(self.key, content); + self.onStoreDraft.dispatch(self, { + expires : expires, + content : content + }); + } + } + }, + + /** + * This method will restore the contents from the storage engine back to the editor. + * + * @method restoreDraft + */ + restoreDraft : function() { + var self = this, storage = self.storage; + + if (storage) { + content = storage.getItem(self.key); + + if (content) { + self.editor.setContent(content); + self.onRestoreDraft.dispatch(self, { + content : content + }); + } + } + }, + + /** + * This method will return true/false if there is a local storage draft available. + * + * @method hasDraft + * @return {boolean} true/false state if there is a local draft. + */ + hasDraft : function() { + var self = this, storage = self.storage, expDate, exists; + + if (storage) { + // Does the item exist at all + exists = !!storage.getItem(self.key); + if (exists) { + // Storage needs autoexpire + if (!self.storage.autoExpires) { + expDate = new Date(storage.getItem(self.key + "_expires")); + + // Contents hasn't expired + if (new Date().getTime() < expDate.getTime()) + return TRUE; + + // Remove it if it has + self.removeDraft(); + } else + return TRUE; + } + } + + return false; + }, + + /** + * Removes the currently stored draft. + * + * @method removeDraft + */ + removeDraft : function() { + var self = this, storage = self.storage, key = self.key, content; + + if (storage) { + // Get current contents and remove the existing draft + content = storage.getItem(key); + storage.removeItem(key); + storage.removeItem(key + "_expires"); + + // Dispatch remove event if we had any contents + if (content) { + self.onRemoveDraft.dispatch(self, { + content : content + }); + } + } + }, + + "static" : { + // Internal unload handler will be called before the page is unloaded + _beforeUnloadHandler : function(e) { + var msg; + + tinymce.each(tinyMCE.editors, function(ed) { + // Store a draft for each editor instance + if (ed.plugins.autosave) + ed.plugins.autosave.storeDraft(); + + // Never ask in fullscreen mode + if (ed.getParam("fullscreen_is_enabled")) + return; + + // Setup a return message if the editor is dirty + if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload")) + msg = ed.getLang("autosave.unload_msg"); + }); + + return msg; + } + } + }); + + tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave); +})(tinymce); diff --git a/web/js/globals/tinymce/plugins/autosave/langs/en.js b/web/js/globals/tinymce/plugins/autosave/langs/en.js new file mode 100644 index 0000000..fce6bd3 --- /dev/null +++ b/web/js/globals/tinymce/plugins/autosave/langs/en.js @@ -0,0 +1,4 @@ +tinyMCE.addI18n('en.autosave',{ +restore_content: "Restore auto-saved content", +warning_message: "If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/bbcode/editor_plugin.js b/web/js/globals/tinymce/plugins/bbcode/editor_plugin.js new file mode 100644 index 0000000..930fdff --- /dev/null +++ b/web/js/globals/tinymce/plugins/bbcode/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(a,b){var d=this,c=a.getParam("bbcode_dialect","punbb").toLowerCase();a.onBeforeSetContent.add(function(e,f){f.content=d["_"+c+"_bbcode2html"](f.content)});a.onPostProcess.add(function(e,f){if(f.set){f.content=d["_"+c+"_bbcode2html"](f.content)}if(f.get){f.content=d["_"+c+"_html2bbcode"](f.content)}})},getInfo:function(){return{longname:"BBCode Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_punbb_html2bbcode:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/(.*?)<\/a>/gi,"[url=$1]$2[/url]");b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/(.*?)<\/span>/gi,"[color=$1]$2[/color]");b(/(.*?)<\/font>/gi,"[color=$1]$2[/color]");b(/(.*?)<\/span>/gi,"[size=$1]$2[/size]");b(/(.*?)<\/font>/gi,"$1");b(//gi,"[img]$1[/img]");b(/(.*?)<\/span>/gi,"[code]$1[/code]");b(/(.*?)<\/span>/gi,"[quote]$1[/quote]");b(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]");b(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]");b(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]");b(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]");b(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]");b(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]");b(/<\/(strong|b)>/gi,"[/b]");b(/<(strong|b)>/gi,"[b]");b(/<\/(em|i)>/gi,"[/i]");b(/<(em|i)>/gi,"[i]");b(/<\/u>/gi,"[/u]");b(/(.*?)<\/span>/gi,"[u]$1[/u]");b(//gi,"[u]");b(/]*>/gi,"[quote]");b(/<\/blockquote>/gi,"[/quote]");b(/
      /gi,"\n");b(//gi,"\n");b(/
      /gi,"\n");b(/

      /gi,"");b(/<\/p>/gi,"\n");b(/ /gi," ");b(/"/gi,'"');b(/</gi,"<");b(/>/gi,">");b(/&/gi,"&");return a},_punbb_bbcode2html:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/\n/gi,"
      ");b(/\[b\]/gi,"");b(/\[\/b\]/gi,"");b(/\[i\]/gi,"");b(/\[\/i\]/gi,"");b(/\[u\]/gi,"");b(/\[\/u\]/gi,"");b(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'$2');b(/\[url\](.*?)\[\/url\]/gi,'$1');b(/\[img\](.*?)\[\/img\]/gi,'');b(/\[color=(.*?)\](.*?)\[\/color\]/gi,'$2');b(/\[code\](.*?)\[\/code\]/gi,'$1 ');b(/\[quote.*?\](.*?)\[\/quote\]/gi,'$1 ');return a}});tinymce.PluginManager.add("bbcode",tinymce.plugins.BBCodePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/bbcode/editor_plugin_src.js b/web/js/globals/tinymce/plugins/bbcode/editor_plugin_src.js new file mode 100644 index 0000000..5586637 --- /dev/null +++ b/web/js/globals/tinymce/plugins/bbcode/editor_plugin_src.js @@ -0,0 +1,120 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.BBCodePlugin', { + init : function(ed, url) { + var t = this, dialect = ed.getParam('bbcode_dialect', 'punbb').toLowerCase(); + + ed.onBeforeSetContent.add(function(ed, o) { + o.content = t['_' + dialect + '_bbcode2html'](o.content); + }); + + ed.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = t['_' + dialect + '_bbcode2html'](o.content); + + if (o.get) + o.content = t['_' + dialect + '_html2bbcode'](o.content); + }); + }, + + getInfo : function() { + return { + longname : 'BBCode Plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + // HTML -> BBCode in PunBB dialect + _punbb_html2bbcode : function(s) { + s = tinymce.trim(s); + + function rep(re, str) { + s = s.replace(re, str); + }; + + // example: to [b] + rep(/(.*?)<\/a>/gi,"[url=$1]$2[/url]"); + rep(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"); + rep(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"); + rep(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"); + rep(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"); + rep(/(.*?)<\/span>/gi,"[color=$1]$2[/color]"); + rep(/(.*?)<\/font>/gi,"[color=$1]$2[/color]"); + rep(/(.*?)<\/span>/gi,"[size=$1]$2[/size]"); + rep(/(.*?)<\/font>/gi,"$1"); + rep(//gi,"[img]$1[/img]"); + rep(/(.*?)<\/span>/gi,"[code]$1[/code]"); + rep(/(.*?)<\/span>/gi,"[quote]$1[/quote]"); + rep(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"); + rep(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"); + rep(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"); + rep(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"); + rep(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"); + rep(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"); + rep(/<\/(strong|b)>/gi,"[/b]"); + rep(/<(strong|b)>/gi,"[b]"); + rep(/<\/(em|i)>/gi,"[/i]"); + rep(/<(em|i)>/gi,"[i]"); + rep(/<\/u>/gi,"[/u]"); + rep(/(.*?)<\/span>/gi,"[u]$1[/u]"); + rep(//gi,"[u]"); + rep(/]*>/gi,"[quote]"); + rep(/<\/blockquote>/gi,"[/quote]"); + rep(/
      /gi,"\n"); + rep(//gi,"\n"); + rep(/
      /gi,"\n"); + rep(/

      /gi,""); + rep(/<\/p>/gi,"\n"); + rep(/ /gi," "); + rep(/"/gi,"\""); + rep(/</gi,"<"); + rep(/>/gi,">"); + rep(/&/gi,"&"); + + return s; + }, + + // BBCode -> HTML from PunBB dialect + _punbb_bbcode2html : function(s) { + s = tinymce.trim(s); + + function rep(re, str) { + s = s.replace(re, str); + }; + + // example: [b] to + rep(/\n/gi,"
      "); + rep(/\[b\]/gi,""); + rep(/\[\/b\]/gi,""); + rep(/\[i\]/gi,""); + rep(/\[\/i\]/gi,""); + rep(/\[u\]/gi,""); + rep(/\[\/u\]/gi,""); + rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,"$2"); + rep(/\[url\](.*?)\[\/url\]/gi,"$1"); + rep(/\[img\](.*?)\[\/img\]/gi,""); + rep(/\[color=(.*?)\](.*?)\[\/color\]/gi,"$2"); + rep(/\[code\](.*?)\[\/code\]/gi,"$1 "); + rep(/\[quote.*?\](.*?)\[\/quote\]/gi,"$1 "); + + return s; + } + }); + + // Register plugin + tinymce.PluginManager.add('bbcode', tinymce.plugins.BBCodePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/contextmenu/editor_plugin.js b/web/js/globals/tinymce/plugins/contextmenu/editor_plugin.js new file mode 100644 index 0000000..9749e51 --- /dev/null +++ b/web/js/globals/tinymce/plugins/contextmenu/editor_plugin.js @@ -0,0 +1 @@ +(function(){var a=tinymce.dom.Event,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.ContextMenu",{init:function(d){var f=this,g;f.editor=d;f.onContextMenu=new tinymce.util.Dispatcher(this);d.onContextMenu.add(function(h,i){if(!i.ctrlKey){if(g){h.selection.setRng(g)}f._getMenu(h).showMenu(i.clientX,i.clientY);a.add(h.getDoc(),"click",function(j){e(h,j)});a.cancel(i)}});d.onRemove.add(function(){if(f._menu){f._menu.removeAll()}});function e(h,i){g=null;if(i&&i.button==2){g=h.selection.getRng();return}if(f._menu){f._menu.removeAll();f._menu.destroy();a.remove(h.getDoc(),"click",e)}}d.onMouseDown.add(e);d.onKeyDown.add(e)},getInfo:function(){return{longname:"Contextmenu",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_getMenu:function(h){var l=this,f=l._menu,i=h.selection,e=i.isCollapsed(),d=i.getNode()||h.getBody(),g,k,j;if(f){f.removeAll();f.destroy()}k=b.getPos(h.getContentAreaContainer());j=b.getPos(h.getContainer());f=h.controlManager.createDropMenu("contextmenu",{offset_x:k.x+h.getParam("contextmenu_offset_x",0),offset_y:k.y+h.getParam("contextmenu_offset_y",0),constrain:1});l._menu=f;f.add({title:"advanced.cut_desc",icon:"cut",cmd:"Cut"}).setDisabled(e);f.add({title:"advanced.copy_desc",icon:"copy",cmd:"Copy"}).setDisabled(e);f.add({title:"advanced.paste_desc",icon:"paste",cmd:"Paste"});if((d.nodeName=="A"&&!h.dom.getAttrib(d,"name"))||!e){f.addSeparator();f.add({title:"advanced.link_desc",icon:"link",cmd:h.plugins.advlink?"mceAdvLink":"mceLink",ui:true});f.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"})}f.addSeparator();f.add({title:"advanced.image_desc",icon:"image",cmd:h.plugins.advimage?"mceAdvImage":"mceImage",ui:true});f.addSeparator();g=f.addMenu({title:"contextmenu.align"});g.add({title:"contextmenu.left",icon:"justifyleft",cmd:"JustifyLeft"});g.add({title:"contextmenu.center",icon:"justifycenter",cmd:"JustifyCenter"});g.add({title:"contextmenu.right",icon:"justifyright",cmd:"JustifyRight"});g.add({title:"contextmenu.full",icon:"justifyfull",cmd:"JustifyFull"});l.onContextMenu.dispatch(l,f,d,e);return f}});tinymce.PluginManager.add("contextmenu",tinymce.plugins.ContextMenu)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/contextmenu/editor_plugin_src.js b/web/js/globals/tinymce/plugins/contextmenu/editor_plugin_src.js new file mode 100644 index 0000000..13813a6 --- /dev/null +++ b/web/js/globals/tinymce/plugins/contextmenu/editor_plugin_src.js @@ -0,0 +1,147 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var Event = tinymce.dom.Event, each = tinymce.each, DOM = tinymce.DOM; + + /** + * This plugin a context menu to TinyMCE editor instances. + * + * @class tinymce.plugins.ContextMenu + */ + tinymce.create('tinymce.plugins.ContextMenu', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @method init + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed) { + var t = this, lastRng; + + t.editor = ed; + + /** + * This event gets fired when the context menu is shown. + * + * @event onContextMenu + * @param {tinymce.plugins.ContextMenu} sender Plugin instance sending the event. + * @param {tinymce.ui.DropMenu} menu Drop down menu to fill with more items if needed. + */ + t.onContextMenu = new tinymce.util.Dispatcher(this); + + ed.onContextMenu.add(function(ed, e) { + if (!e.ctrlKey) { + // Restore the last selection since it was removed + if (lastRng) + ed.selection.setRng(lastRng); + + t._getMenu(ed).showMenu(e.clientX, e.clientY); + Event.add(ed.getDoc(), 'click', function(e) { + hide(ed, e); + }); + Event.cancel(e); + } + }); + + ed.onRemove.add(function() { + if (t._menu) + t._menu.removeAll(); + }); + + function hide(ed, e) { + lastRng = null; + + // Since the contextmenu event moves + // the selection we need to store it away + if (e && e.button == 2) { + lastRng = ed.selection.getRng(); + return; + } + + if (t._menu) { + t._menu.removeAll(); + t._menu.destroy(); + Event.remove(ed.getDoc(), 'click', hide); + } + }; + + ed.onMouseDown.add(hide); + ed.onKeyDown.add(hide); + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @method getInfo + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Contextmenu', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _getMenu : function(ed) { + var t = this, m = t._menu, se = ed.selection, col = se.isCollapsed(), el = se.getNode() || ed.getBody(), am, p1, p2; + + if (m) { + m.removeAll(); + m.destroy(); + } + + p1 = DOM.getPos(ed.getContentAreaContainer()); + p2 = DOM.getPos(ed.getContainer()); + + m = ed.controlManager.createDropMenu('contextmenu', { + offset_x : p1.x + ed.getParam('contextmenu_offset_x', 0), + offset_y : p1.y + ed.getParam('contextmenu_offset_y', 0), + constrain : 1 + }); + + t._menu = m; + + m.add({title : 'advanced.cut_desc', icon : 'cut', cmd : 'Cut'}).setDisabled(col); + m.add({title : 'advanced.copy_desc', icon : 'copy', cmd : 'Copy'}).setDisabled(col); + m.add({title : 'advanced.paste_desc', icon : 'paste', cmd : 'Paste'}); + + if ((el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) || !col) { + m.addSeparator(); + m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); + m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); + } + + m.addSeparator(); + m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); + + m.addSeparator(); + am = m.addMenu({title : 'contextmenu.align'}); + am.add({title : 'contextmenu.left', icon : 'justifyleft', cmd : 'JustifyLeft'}); + am.add({title : 'contextmenu.center', icon : 'justifycenter', cmd : 'JustifyCenter'}); + am.add({title : 'contextmenu.right', icon : 'justifyright', cmd : 'JustifyRight'}); + am.add({title : 'contextmenu.full', icon : 'justifyfull', cmd : 'JustifyFull'}); + + t.onContextMenu.dispatch(t, m, el, col); + + return m; + } + }); + + // Register plugin + tinymce.PluginManager.add('contextmenu', tinymce.plugins.ContextMenu); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/directionality/editor_plugin.js b/web/js/globals/tinymce/plugins/directionality/editor_plugin.js new file mode 100644 index 0000000..bce8e73 --- /dev/null +++ b/web/js/globals/tinymce/plugins/directionality/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.Directionality",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceDirectionLTR",function(){var d=a.dom.getParent(a.selection.getNode(),a.dom.isBlock);if(d){if(a.dom.getAttrib(d,"dir")!="ltr"){a.dom.setAttrib(d,"dir","ltr")}else{a.dom.setAttrib(d,"dir","")}}a.nodeChanged()});a.addCommand("mceDirectionRTL",function(){var d=a.dom.getParent(a.selection.getNode(),a.dom.isBlock);if(d){if(a.dom.getAttrib(d,"dir")!="rtl"){a.dom.setAttrib(d,"dir","rtl")}else{a.dom.setAttrib(d,"dir","")}}a.nodeChanged()});a.addButton("ltr",{title:"directionality.ltr_desc",cmd:"mceDirectionLTR"});a.addButton("rtl",{title:"directionality.rtl_desc",cmd:"mceDirectionRTL"});a.onNodeChange.add(c._nodeChange,c)},getInfo:function(){return{longname:"Directionality",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_nodeChange:function(b,a,e){var d=b.dom,c;e=d.getParent(e,d.isBlock);if(!e){a.setDisabled("ltr",1);a.setDisabled("rtl",1);return}c=d.getAttrib(e,"dir");a.setActive("ltr",c=="ltr");a.setDisabled("ltr",0);a.setActive("rtl",c=="rtl");a.setDisabled("rtl",0)}});tinymce.PluginManager.add("directionality",tinymce.plugins.Directionality)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/directionality/editor_plugin_src.js b/web/js/globals/tinymce/plugins/directionality/editor_plugin_src.js new file mode 100644 index 0000000..4444959 --- /dev/null +++ b/web/js/globals/tinymce/plugins/directionality/editor_plugin_src.js @@ -0,0 +1,82 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Directionality', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + ed.addCommand('mceDirectionLTR', function() { + var e = ed.dom.getParent(ed.selection.getNode(), ed.dom.isBlock); + + if (e) { + if (ed.dom.getAttrib(e, "dir") != "ltr") + ed.dom.setAttrib(e, "dir", "ltr"); + else + ed.dom.setAttrib(e, "dir", ""); + } + + ed.nodeChanged(); + }); + + ed.addCommand('mceDirectionRTL', function() { + var e = ed.dom.getParent(ed.selection.getNode(), ed.dom.isBlock); + + if (e) { + if (ed.dom.getAttrib(e, "dir") != "rtl") + ed.dom.setAttrib(e, "dir", "rtl"); + else + ed.dom.setAttrib(e, "dir", ""); + } + + ed.nodeChanged(); + }); + + ed.addButton('ltr', {title : 'directionality.ltr_desc', cmd : 'mceDirectionLTR'}); + ed.addButton('rtl', {title : 'directionality.rtl_desc', cmd : 'mceDirectionRTL'}); + + ed.onNodeChange.add(t._nodeChange, t); + }, + + getInfo : function() { + return { + longname : 'Directionality', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + _nodeChange : function(ed, cm, n) { + var dom = ed.dom, dir; + + n = dom.getParent(n, dom.isBlock); + if (!n) { + cm.setDisabled('ltr', 1); + cm.setDisabled('rtl', 1); + return; + } + + dir = dom.getAttrib(n, 'dir'); + cm.setActive('ltr', dir == "ltr"); + cm.setDisabled('ltr', 0); + cm.setActive('rtl', dir == "rtl"); + cm.setDisabled('rtl', 0); + } + }); + + // Register plugin + tinymce.PluginManager.add('directionality', tinymce.plugins.Directionality); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/emotions/editor_plugin.js b/web/js/globals/tinymce/plugins/emotions/editor_plugin.js new file mode 100644 index 0000000..dbdd8ff --- /dev/null +++ b/web/js/globals/tinymce/plugins/emotions/editor_plugin.js @@ -0,0 +1 @@ +(function(a){a.create("tinymce.plugins.EmotionsPlugin",{init:function(b,c){b.addCommand("mceEmotion",function(){b.windowManager.open({file:c+"/emotions.htm",width:250+parseInt(b.getLang("emotions.delta_width",0)),height:160+parseInt(b.getLang("emotions.delta_height",0)),inline:1},{plugin_url:c})});b.addButton("emotions",{title:"emotions.emotions_desc",cmd:"mceEmotion"})},getInfo:function(){return{longname:"Emotions",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions",version:a.majorVersion+"."+a.minorVersion}}});a.PluginManager.add("emotions",a.plugins.EmotionsPlugin)})(tinymce); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/emotions/editor_plugin_src.js b/web/js/globals/tinymce/plugins/emotions/editor_plugin_src.js new file mode 100644 index 0000000..71d5416 --- /dev/null +++ b/web/js/globals/tinymce/plugins/emotions/editor_plugin_src.js @@ -0,0 +1,43 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + tinymce.create('tinymce.plugins.EmotionsPlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceEmotion', function() { + ed.windowManager.open({ + file : url + '/emotions.htm', + width : 250 + parseInt(ed.getLang('emotions.delta_width', 0)), + height : 160 + parseInt(ed.getLang('emotions.delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register buttons + ed.addButton('emotions', {title : 'emotions.emotions_desc', cmd : 'mceEmotion'}); + }, + + getInfo : function() { + return { + longname : 'Emotions', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('emotions', tinymce.plugins.EmotionsPlugin); +})(tinymce); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/emotions/emotions.htm b/web/js/globals/tinymce/plugins/emotions/emotions.htm new file mode 100644 index 0000000..55a1d72 --- /dev/null +++ b/web/js/globals/tinymce/plugins/emotions/emotions.htm @@ -0,0 +1,40 @@ + + + + {#emotions_dlg.title} + + + + +

      +
      {#emotions_dlg.title}:

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      {#emotions_dlg.cool}{#emotions_dlg.cry}{#emotions_dlg.embarassed}{#emotions_dlg.foot_in_mouth}
      {#emotions_dlg.frown}{#emotions_dlg.innocent}{#emotions_dlg.kiss}{#emotions_dlg.laughing}
      {#emotions_dlg.money_mouth}{#emotions_dlg.sealed}{#emotions_dlg.smile}{#emotions_dlg.surprised}
      {#emotions_dlg.tongue-out}{#emotions_dlg.undecided}{#emotions_dlg.wink}{#emotions_dlg.yell}
      +
      + + diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-cool.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-cool.gif new file mode 100644 index 0000000000000000000000000000000000000000..ba90cc36fb0415d0273d1cd206bff63fd9c91fde GIT binary patch literal 354 zcmV-o0iFIwNk%w1VG;lm0Mr!#3ke00dJfFY%i+lrhK7V(RutUQJhPY;?(XfrsZKgL z7WLQ^zPO&zzav{)SL^9nBOw~z(=orMEH5uC-P_gr`uhCnASMa|$-iRw?m_(dUwU8) zq>Kx}s1_F$4FCWDA^8LW0018VEC2ui01^Na000Hw;3tYzX_jM3Qpv$_M?zI9i5=0S zX-{-uv=l3%&P0s%m9Ox_a(m_c|u z01g3U0`Wll5)poVdma=N8y<3f0Sf~hXmTC}2oxMW4FdxUj+z4<0}lrX2nP=qkDRIt z9Ge*(qzMrj3jrIOjvI{`5eWzt3`G_T8yChG8w(a19SkK12@M(+799Zr9n=~PzBCmA z5)BU-)YKUd4H5!D9|!^o9kWIe9SH(WDHRk92}DZ?3})2$P@$55g90f0N)ZA8JID5J Aw*UYD literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-cry.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-cry.gif new file mode 100644 index 0000000000000000000000000000000000000000..74d897a4f6d22e814e2b054e98b8a75fb464b4be GIT binary patch literal 329 zcmV-P0k-}}Nk%w1VG;lm0Mr-&E)xPSit@9T3%;vR+|V+?t0A(pllJjXrMl7n=_A_a za^B+Su$LjvyC3@TIQZNZa##w=!k(SO^P#bO*w(eU#;{U83XFCU_V)J5wrb+;g2vkN z#>U24qVoOvY5)KLA^8LW0018VEC2ui01^Na000HX;3tY$X_jM3QUfCh%s^o(nF++< zc?Th6v=oL>*by8K!mhvwelUXuuW&&U9iGO3hM@>Njw{l^#0q9mWpcefdI;O$;efnY zkd~@r-o$*74FCWI1%d((4+jDz0va0>69^fI6%`W{8w!gU1pyL>prH>E0R<%k6Aq%H z4ij+^9TEwM5P}eh2@)L<~6+>@EpxfA0YrcPNsSu literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-embarassed.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-embarassed.gif new file mode 100644 index 0000000000000000000000000000000000000000..963a96b8a7593b1d8bcbab073abe5ee4e539dbf6 GIT binary patch literal 331 zcmV-R0kr-{Nk%w1VG;lm0MrryDh>j~yq&6%75dW~z^P39(NxsGDE{UkxtkIEq(S-a zRKlwv+S=Lr?>hbYY~sQ?c3T&ZcN_Nh_EU3s(>Io6B&>WW`@bsw**)Ocy1bht z{*G6|uwwqUQ2+n{A^8LW0018VEC2ui01^Na000HZ;3tYwX_jM3YQ!c88=*-m*&&bO zILd=`w3KAC;8hxpif*w9ek6oqV-Z0L77fROK$BSR@5BAv-%C>6y>>#+D4e#&nz^qMDItlpp zTG728+|V&?R13PIEBW(C`uh6d*t-1sZ^XQv;oDD}iYLOV7uVO;{`xl4#4tJ{0;h@! z>)kfFn;iS@Hvj+tA^8LW0018VEC2ui01^Na000Hm;3tYuX_jM3Mo7199TGt*Nf;R= zNmOPKwA8_2Q6MTDP6eT`I1VESVj-zGIG(JdB3U44kcdI@;AAq{Gv^^O%%ltj2GdB) z>vIL;d*~=0a|w1Bf^!cF9R~+vb94;_0}TxWlnMrlj2MuVoSYAreF`3(0|pHS8VLgr zi3bP_qZ;q#>Sw62=mns-On=0wransPVevT^YK{Dy(0YY zH)vE6x0?;Wqb>gZas1^OT0si>`ugD5y87}*#H$s=yq(wA*8cf7{`y+(+9J7|9QfT7 z`ROHiU=Y&6FaQ7mA^8LW0018VEC2ui01^Na000Hi;3tYvX_jM3N`@u~nju9hSuh^r zIEcp-wA7(NL0~2d#RP+(G!CPPA>o*KJjv_CkucCA5=K?AfF#RG2V*8BU@jL304|4P z2;PGRF@bj$et;Jf2pR_mVsIA<85|n}kQ*Bq42Ovqj*yy>6P0=h3X&9Z01yyk~2N4w%7#RW^55W%`0vQ+-6(y_*2pqz~90*;x9}yM}%$UI(7t#$D mK_3Se1{4HKM+6iG7EmeH6$V631{L5n)#CyC0qx-*Apkoyg?w!Q literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-innocent.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-innocent.gif new file mode 100644 index 0000000000000000000000000000000000000000..334d49e0e60f2997c9ba24071764f95d9e08a5cc GIT binary patch literal 336 zcmV-W0k8f?Nk%w1VG;lm0MrryI4TI-%dP0m5~*+Y`T~ z7Rth){q{I_X%*S48uRZ|(b3V&wIKTX`u+WJzo<^$#wuY;3W|Cf{O29IkTAcaE&lpe z+P*^H)-tknA^-pYA^8LW0018VEC2ui01^Na000He;3tYwX_n)75QgVvNQ`6#5gcMm zEEG~blgXokptKAJgCU?%JT?yos!R6cPtcQWh2siHlNI2L}ifQhgX02^InZ2?-ktkqVRyZJY^Trk|lv zovp437?1~d46O)?2(1i+2NDYk8<+_Kil!K!3njA^!I#dL8x<729}*B65mC=m5gHH@ iDi9P3f*VjB3KS4HDb_qqRul{0DIT=Nk%w1VG;lm0Mrx!QauaC#>Vb6G=_5=^YB^9wrc376Sb5I-qJGf@9vZ# z5WlKU(!eVB+7tfnDXp0zyB`?BZ5IChalob*`uh6d*t+@dKGHcU+L|83yq*5~IoH?L zy`?Gp<{bX|SpWb4A^8LW0018VEC2ui01^Na000Hg;3tYyX_jM3R?Bl7&r(q;SsVx< zNd$5fv{ZsKA$SlL3&KN~a1tZRf*~1Ltkx9~2uL3&z-yb0WJDRY082|tP literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-laughing.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-laughing.gif new file mode 100644 index 0000000000000000000000000000000000000000..1606c119e75678c4031f384e0d50849906e8f533 GIT binary patch literal 344 zcmV-e0jK^)Nk%w1VG;lm0MruzQauf>s;1-69HWK?p_PpF=Pd8~Ygtcnp*fHAL z**;z>w3iC}`fmL6IkKB1N;3zEa}&zKpsu1;_V)HocR5-{J~BcYvE`YXhBnc@CfU=! za(Ec zG>66zv=rqr;2j)}gKqE$ekcSD?}0=WLB?AWp85)qALd+P=4)6X4oXy{bw2>K^d$ z@6ERvva+(4ib~41YUkTEn1&#?rzrOHT>1I=Y*h`+%*@WtPUPg|!@EEI_d5LgZ>^Og z-qyCjsu$J9F8}}lA^8LW0018VEC2ui01^Na000HT;3tYxX_jM37RWXX8&XUv=@{Oj zX@_Sxw3H&!kzgQ?2LvPOL=>Y5VxieY9+_+eqFEql6OKWXd3Ze8Ggf2Zln@U|mI9d9 zGm^(wVUTA5cYs-V1`2#+a})^z6chrF5`~8k5e6@pmkW`GeGw<069yTQaGnH)s0suV zR|pCd0ZtRCsjM9VB^L+~7X%f*zyuc%2p3=#ycf#L%McYo9|{Z&5D^#_78qL%3{WW( X7Xb)FP6z?UH6ODVz!ev-DIowmgll^P literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-smile.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-smile.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6a9e60d5ddd1243fbbf2197b4dc6cd9c1b58b93 GIT binary patch literal 345 zcmV-f0jB;(Nk%w1VG;lm0MrlwCJF+^#>SR<4C>Dj%C>6W(lWoQPVevT^YB^Fy&h6M z4YZgH{O~qtR1(Ci8T;lQ`uh6d*t-7xar*K{#Jrulo-Wtd*44u?{`oh#n;gQXGXDEo z_}UVAU=FH^0ssI2A^8LW0018VEC2ui01^Na000Hn;3tYuX_jM3Mn>j&nGr!MNh}v4 zNyxPjwA7*EKx`%q#$Vl9SM>N9ReH-cn1&^4jYXf0KotqjT;UWC94U(4-NtX4#i!%9}pHA2?&dg3>XLr r8Wuqx2Nnhn1xrT-4h9xbDb^GQ8V(K`1{C5o)#U;I0p5-K5CQ-@9%ySnDDC*4*{OcpiwransPVevTQacIr@mkQp zCf(06s)_=>r7UYx48o@u`uh6d*t-7rH~ji<`P&oj;5Wp)o!8ga`SV6TA_BIW5#ZWV z{`*+__>9}pJ}3JDSl85wB_3Jn)Q o9|so(4+|I^92g4^1{Y8%(iR3pP6ig=HPPY$`~mLZA3^{CJDB=?L;wH) literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-tongue-out.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-tongue-out.gif new file mode 100644 index 0000000000000000000000000000000000000000..2075dc16058f1f17912167675ce5cfb9986fc71d GIT binary patch literal 328 zcmV-O0k{4~Nk%w1VG;lm0Mrx!CJF+^#>SU@3-{U*rx+Q^wrc$ABfqLn@9*x?z8(4X zSW-O=@){bmmI~g|GQXoP);cvj3|f1M8e@{G*!tYaiCEujj1NGxRN#6#tiCETo+{x{Hkzt z5k-kPvcD=V2nbmjCgL6k{uF&2nP-t0s;w<385Nx2oxDb z9T5Pp7qJl?3Kkh9oe2sCr5F$p7zPSlsUH*@54w*83=9Or4;w)r2pcU95(FL|1Th;< aDaRQH4;Tal7#Y$v#?=Au0pHUfApkpvZg^t= literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-undecided.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-undecided.gif new file mode 100644 index 0000000000000000000000000000000000000000..bef7e257303f8243c89787e7a7f9955dd1f112e2 GIT binary patch literal 337 zcmV-X0j~Z>Nk%w1VG;lm0MroxDi#99#>R?y8~4}{%C>6#>?OadPVevTr-=vi@LATn z4rERY-qJF+n+?CCE&B3D{{3Shh?>WT0o%`b%*Voqm`dL;(4F35y zc485^n;g!+Bme*aA^8LW0018VEC2ui01^Na000Hf;3tYvX_jM3N=AnuogqakNi<9X zK?&0kwA8^tNn{?C$|IAYI1ZzT!2>}iuMddFK#NEkRl!7%6brJAnUs;)XcnA}TNBSP zxQ9;SvEfwYeSaGd2^|LqU~(QF1qBxr3Ii7x84ZVt8wCTKoSYAqc?p`G2onnpk`IOl z1`HLGj}riN2p1K12N4z&8IBDc6tEWs859;JtRB6>lf+xO9}yT19toMv8wnl`7(pKg j7zPv!OGgY81{hE&(iR3pP6ig;HPPS!_yOwPA0Yrc)=Yf3 literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-wink.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-wink.gif new file mode 100644 index 0000000000000000000000000000000000000000..9faf1aff8f4b28e02f4f414975fe1859c43b6b54 GIT binary patch literal 351 zcmV-l0igazNk%w1VG;lm0MrryC=CL}#>Sn03F^-g-qAA3wransPV?|t@9*x%vmQ`7 z4E*pcw3rOOq%3t@4*K#({N^40{c-yG`rz2Q!KfI-yq*61HrBop*VoqW<}&{JS@_x# zwwfH#!YTdnIsgCwA^8LW0018VEC2ui01^Na000Ht;3tYwX_jM3P6j6koH0o%Sun&A zMF+tYv=pL2IcOdp&qH&dG!P?+ArV0)J)O=Yk}%LD6Go&#@MJn3he8=)%%lWOM*#pN zEDD9iq9J$@90v~;83`GC4i0+{2OJ0pVtacF5E}yn8<`pmkCBv_pqZEtoPY-l0}P>= z3WE6cr`19U7DgF9{F}at6R35*Q5~ x2OgBy9tRx_7(pKh7zPvsOGgA01{hE&-4zBzP6id}HMp@0Krnzkbss_i06S`>cdh^c literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/emotions/img/smiley-yell.gif b/web/js/globals/tinymce/plugins/emotions/img/smiley-yell.gif new file mode 100644 index 0000000000000000000000000000000000000000..648e6e879123fe49beebbc1f3635141864a79a9c GIT binary patch literal 336 zcmV-W0k8f?Nk%w1VG;lm0MrryG8O{K#>IbS7WCB_mWF$+hzY-{PWkp(?(Xf;zbH~P z3jOdj?W+^YwrakfE8fyG&5jTBz!3WS`fgM_;MltQ+c}4GO8)(E`S3`@yq&d~5!ct& z)v79NObo)O7XSbNA^8LW0018VEC2ui01^Na000He;3tYwX_jM3QifI(nn6h_*=Wyk zUB{y}v=qYOIUF#R3dZPhAVv~H;(|a2yN_5FH&J0|$eJ3kw4gj1Y?v5d#>LMV12^6BYy$1)ZKA zga!|m2?POz0R)f>4+aPl8KD{gz`+G_9vLMFQU?RU!8uyH9}*i52|cC+7S0YEK_3Vk i1|APfM-Ltb8&4_H83sg61{vHn(cc000qNZzApkp + + + {#example_dlg.title} + + + + + +
      +

      Here is a example dialog.

      +

      Selected text:

      +

      Custom arg:

      + +
      + + +
      +
      + + + diff --git a/web/js/globals/tinymce/plugins/example/editor_plugin.js b/web/js/globals/tinymce/plugins/example/editor_plugin.js new file mode 100644 index 0000000..ec1f81e --- /dev/null +++ b/web/js/globals/tinymce/plugins/example/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.PluginManager.requireLangPack("example");tinymce.create("tinymce.plugins.ExamplePlugin",{init:function(a,b){a.addCommand("mceExample",function(){a.windowManager.open({file:b+"/dialog.htm",width:320+parseInt(a.getLang("example.delta_width",0)),height:120+parseInt(a.getLang("example.delta_height",0)),inline:1},{plugin_url:b,some_custom_arg:"custom arg"})});a.addButton("example",{title:"example.desc",cmd:"mceExample",image:b+"/img/example.gif"});a.onNodeChange.add(function(d,c,e){c.setActive("example",e.nodeName=="IMG")})},createControl:function(b,a){return null},getInfo:function(){return{longname:"Example plugin",author:"Some author",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/example",version:"1.0"}}});tinymce.PluginManager.add("example",tinymce.plugins.ExamplePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/example/editor_plugin_src.js b/web/js/globals/tinymce/plugins/example/editor_plugin_src.js new file mode 100644 index 0000000..9a0e7da --- /dev/null +++ b/web/js/globals/tinymce/plugins/example/editor_plugin_src.js @@ -0,0 +1,84 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + // Load plugin specific language pack + tinymce.PluginManager.requireLangPack('example'); + + tinymce.create('tinymce.plugins.ExamplePlugin', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed, url) { + // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample'); + ed.addCommand('mceExample', function() { + ed.windowManager.open({ + file : url + '/dialog.htm', + width : 320 + parseInt(ed.getLang('example.delta_width', 0)), + height : 120 + parseInt(ed.getLang('example.delta_height', 0)), + inline : 1 + }, { + plugin_url : url, // Plugin absolute URL + some_custom_arg : 'custom arg' // Custom argument + }); + }); + + // Register example button + ed.addButton('example', { + title : 'example.desc', + cmd : 'mceExample', + image : url + '/img/example.gif' + }); + + // Add a node change handler, selects the button in the UI when a image is selected + ed.onNodeChange.add(function(ed, cm, n) { + cm.setActive('example', n.nodeName == 'IMG'); + }); + }, + + /** + * Creates control instances based in the incomming name. This method is normally not + * needed since the addButton method of the tinymce.Editor class is a more easy way of adding buttons + * but you sometimes need to create more complex controls like listboxes, split buttons etc then this + * method can be used to create those. + * + * @param {String} n Name of the control to create. + * @param {tinymce.ControlManager} cm Control manager to use inorder to create new control. + * @return {tinymce.ui.Control} New control instance or null if no control was created. + */ + createControl : function(n, cm) { + return null; + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Example plugin', + author : 'Some author', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/example', + version : "1.0" + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('example', tinymce.plugins.ExamplePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/example/img/example.gif b/web/js/globals/tinymce/plugins/example/img/example.gif new file mode 100644 index 0000000000000000000000000000000000000000..1ab5da4461113d2af579898528246fdbe52ecd00 GIT binary patch literal 87 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz@*pGzw+SQ`#f{}FJ-?!v#V)e mtsGNfpJeCKSAiOz**>0`XR2{OVa>-G_df0vaY/i);if(f&&f[1]){l=f[1].match(/\s*(\w+\s*=\s*".*?"|\w+\s*=\s*'.*?'|\w+\s*=\s*\w+|\w+)\s*/g);if(l){for(c=0,e=l.length;c",a);h.head=f.substring(0,a+1);j=f.indexOf("\n'}h.head+=d.getParam("fullpage_default_doctype",'');h.head+="\n\n\n"+d.getParam("fullpage_default_title","Untitled document")+"\n";if(g=d.getParam("fullpage_default_encoding")){h.head+='\n'}if(g=d.getParam("fullpage_default_font_family")){i+="font-family: "+g+";"}if(g=d.getParam("fullpage_default_font_size")){i+="font-size: "+g+";"}if(g=d.getParam("fullpage_default_text_color")){i+="color: "+g+";"}h.head+="\n\n";h.foot="\n\n"}},_getContent:function(a,c){var b=this;if(!c.source_view||!a.getParam("fullpage_hide_in_source_view")){c.content=tinymce.trim(b.head)+"\n"+tinymce.trim(c.content)+"\n"+tinymce.trim(b.foot)}}});tinymce.PluginManager.add("fullpage",tinymce.plugins.FullPagePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/fullpage/editor_plugin_src.js b/web/js/globals/tinymce/plugins/fullpage/editor_plugin_src.js new file mode 100644 index 0000000..a2c9df8 --- /dev/null +++ b/web/js/globals/tinymce/plugins/fullpage/editor_plugin_src.js @@ -0,0 +1,153 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.FullPagePlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceFullPageProperties', function() { + ed.windowManager.open({ + file : url + '/fullpage.htm', + width : 430 + parseInt(ed.getLang('fullpage.delta_width', 0)), + height : 495 + parseInt(ed.getLang('fullpage.delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + head_html : t.head + }); + }); + + // Register buttons + ed.addButton('fullpage', {title : 'fullpage.desc', cmd : 'mceFullPageProperties'}); + + ed.onBeforeSetContent.add(t._setContent, t); + ed.onSetContent.add(t._setBodyAttribs, t); + ed.onGetContent.add(t._getContent, t); + }, + + getInfo : function() { + return { + longname : 'Fullpage', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private plugin internal methods + + _setBodyAttribs : function(ed, o) { + var bdattr, i, len, kv, k, v, t, attr = this.head.match(/body(.*?)>/i); + + if (attr && attr[1]) { + bdattr = attr[1].match(/\s*(\w+\s*=\s*".*?"|\w+\s*=\s*'.*?'|\w+\s*=\s*\w+|\w+)\s*/g); + + if (bdattr) { + for(i = 0, len = bdattr.length; i < len; i++) { + kv = bdattr[i].split('='); + k = kv[0].replace(/\s/,''); + v = kv[1]; + + if (v) { + v = v.replace(/^\s+/,'').replace(/\s+$/,''); + t = v.match(/^["'](.*)["']$/); + + if (t) + v = t[1]; + } else + v = k; + + ed.dom.setAttrib(ed.getBody(), 'style', v); + } + } + } + }, + + _createSerializer : function() { + return new tinymce.dom.Serializer({ + dom : this.editor.dom, + apply_source_formatting : true + }); + }, + + _setContent : function(ed, o) { + var t = this, sp, ep, c = o.content, v, st = ''; + + // Ignore raw updated if we already have a head, this will fix issues with undo/redo keeping the head/foot separate + if (o.format == 'raw' && t.head) + return; + + if (o.source_view && ed.getParam('fullpage_hide_in_source_view')) + return; + + // Parse out head, body and footer + c = c.replace(/<(\/?)BODY/gi, '<$1body'); + sp = c.indexOf('', sp); + t.head = c.substring(0, sp + 1); + + ep = c.indexOf('\n'; + + t.head += ed.getParam('fullpage_default_doctype', ''); + t.head += '\n\n\n' + ed.getParam('fullpage_default_title', 'Untitled document') + '\n'; + + if (v = ed.getParam('fullpage_default_encoding')) + t.head += '\n'; + + if (v = ed.getParam('fullpage_default_font_family')) + st += 'font-family: ' + v + ';'; + + if (v = ed.getParam('fullpage_default_font_size')) + st += 'font-size: ' + v + ';'; + + if (v = ed.getParam('fullpage_default_text_color')) + st += 'color: ' + v + ';'; + + t.head += '\n\n'; + t.foot = '\n\n'; + } + }, + + _getContent : function(ed, o) { + var t = this; + + if (!o.source_view || !ed.getParam('fullpage_hide_in_source_view')) + o.content = tinymce.trim(t.head) + '\n' + tinymce.trim(o.content) + '\n' + tinymce.trim(t.foot); + } + }); + + // Register plugin + tinymce.PluginManager.add('fullpage', tinymce.plugins.FullPagePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/fullpage/fullpage.htm b/web/js/globals/tinymce/plugins/fullpage/fullpage.htm new file mode 100644 index 0000000..c32afaf --- /dev/null +++ b/web/js/globals/tinymce/plugins/fullpage/fullpage.htm @@ -0,0 +1,571 @@ + + + + {#fullpage_dlg.title} + + + + + + + +
      + + +
      +
      +
      + {#fullpage_dlg.meta_props} + + + + + + + + + + + + + + + + + + + + + + + + + + +
       
       
       
       
       
        + +
      +
      + +
      + {#fullpage_dlg.langprops} + + + + + + + + + + + + + + + + + + + + + + +
      + +
        + +
       
      + +
       
      +
      +
      + +
      +
      + {#fullpage_dlg.appearance_textprops} + + + + + + + + + + + + + + + + +
      + +
      + +
      + + + + + +
       
      +
      +
      + +
      + {#fullpage_dlg.appearance_bgprops} + + + + + + + + + + +
      + + + + + +
       
      +
      + + + + + +
       
      +
      +
      + +
      + {#fullpage_dlg.appearance_marginprops} + + + + + + + + + + + + + + +
      +
      + +
      + {#fullpage_dlg.appearance_linkprops} + + + + + + + + + + + + + + + + + + + +
      + + + + + +
      +
      + + + + + +
       
      +
      + + + + + +
       
      +
        
      +
      + +
      + {#fullpage_dlg.appearance_style} + + + + + + + + + + +
      + + + + +
       
      +
      +
      + +
      + + +
      + {#fullpage_dlg.head_elements} + +
      +
      +
      + + +
      +
      + + +
      +
      +
      + +
      +
      + +
      + {#fullpage_dlg.meta_element} + + + + + + + + + + + + + + +
      + + +
      + +
      + {#fullpage_dlg.title_element} + + + + + + +
      + + +
      + +
      + {#fullpage_dlg.script_element} + + + +
      + +
      +
      + + + + + + + + + + + + + + + + + +
      + + + + +
       
      +
      + +
      + +
      +
      + + +
      + +
      + {#fullpage_dlg.style_element} + + + +
      + +
      +
      + + + + + + + + + +
      +
      + +
      + +
      +
      + + +
      + +
      + {#fullpage_dlg.base_element} + + + + + + + + + + +
      + + +
      + + + +
      + {#fullpage_dlg.comment_element} + + + + +
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/fullpage/js/fullpage.js b/web/js/globals/tinymce/plugins/fullpage/js/fullpage.js new file mode 100644 index 0000000..a1bb719 --- /dev/null +++ b/web/js/globals/tinymce/plugins/fullpage/js/fullpage.js @@ -0,0 +1,471 @@ +/** + * fullpage.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinyMCEPopup.requireLangPack(); + +var doc; + +var defaultDocTypes = + 'XHTML 1.0 Transitional=,' + + 'XHTML 1.0 Frameset=,' + + 'XHTML 1.0 Strict=,' + + 'XHTML 1.1=,' + + 'HTML 4.01 Transitional=,' + + 'HTML 4.01 Strict=,' + + 'HTML 4.01 Frameset='; + +var defaultEncodings = + 'Western european (iso-8859-1)=iso-8859-1,' + + 'Central European (iso-8859-2)=iso-8859-2,' + + 'Unicode (UTF-8)=utf-8,' + + 'Chinese traditional (Big5)=big5,' + + 'Cyrillic (iso-8859-5)=iso-8859-5,' + + 'Japanese (iso-2022-jp)=iso-2022-jp,' + + 'Greek (iso-8859-7)=iso-8859-7,' + + 'Korean (iso-2022-kr)=iso-2022-kr,' + + 'ASCII (us-ascii)=us-ascii'; + +var defaultMediaTypes = + 'all=all,' + + 'screen=screen,' + + 'print=print,' + + 'tty=tty,' + + 'tv=tv,' + + 'projection=projection,' + + 'handheld=handheld,' + + 'braille=braille,' + + 'aural=aural'; + +var defaultFontNames = 'Arial=arial,helvetica,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,times new roman,times,serif;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times,serif;Verdana=verdana,arial,helvetica,sans-serif;Impact=impact;WingDings=wingdings'; +var defaultFontSizes = '10px,11px,12px,13px,14px,15px,16px'; + +function init() { + var f = document.forms['fullpage'], el = f.elements, e, i, p, doctypes, encodings, mediaTypes, fonts, ed = tinyMCEPopup.editor, dom = tinyMCEPopup.dom, style; + + // Setup doctype select box + doctypes = ed.getParam("fullpage_doctypes", defaultDocTypes).split(','); + for (i=0; i 1) + addSelectValue(f, 'doctypes', p[0], p[1]); + } + + // Setup fonts select box + fonts = ed.getParam("fullpage_fonts", defaultFontNames).split(';'); + for (i=0; i 1) + addSelectValue(f, 'fontface', p[0], p[1]); + } + + // Setup fontsize select box + fonts = ed.getParam("fullpage_fontsizes", defaultFontSizes).split(','); + for (i=0; i 1) { + addSelectValue(f, 'element_style_media', p[0], p[1]); + addSelectValue(f, 'element_link_media', p[0], p[1]); + } + } + + // Setup encodings select box + encodings = ed.getParam("fullpage_encodings", defaultEncodings).split(','); + for (i=0; i 1) { + addSelectValue(f, 'docencoding', p[0], p[1]); + addSelectValue(f, 'element_script_charset', p[0], p[1]); + addSelectValue(f, 'element_link_charset', p[0], p[1]); + } + } + + document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); + document.getElementById('link_color_pickcontainer').innerHTML = getColorPickerHTML('link_color_pick','link_color'); + //document.getElementById('hover_color_pickcontainer').innerHTML = getColorPickerHTML('hover_color_pick','hover_color'); + document.getElementById('visited_color_pickcontainer').innerHTML = getColorPickerHTML('visited_color_pick','visited_color'); + document.getElementById('active_color_pickcontainer').innerHTML = getColorPickerHTML('active_color_pick','active_color'); + document.getElementById('textcolor_pickcontainer').innerHTML = getColorPickerHTML('textcolor_pick','textcolor'); + document.getElementById('stylesheet_browsercontainer').innerHTML = getBrowserHTML('stylesheetbrowser','stylesheet','file','fullpage'); + document.getElementById('link_href_pickcontainer').innerHTML = getBrowserHTML('link_href_browser','element_link_href','file','fullpage'); + document.getElementById('script_src_pickcontainer').innerHTML = getBrowserHTML('script_src_browser','element_script_src','file','fullpage'); + document.getElementById('bgimage_pickcontainer').innerHTML = getBrowserHTML('bgimage_browser','bgimage','image','fullpage'); + + // Resize some elements + if (isVisible('stylesheetbrowser')) + document.getElementById('stylesheet').style.width = '220px'; + + if (isVisible('link_href_browser')) + document.getElementById('element_link_href').style.width = '230px'; + + if (isVisible('bgimage_browser')) + document.getElementById('bgimage').style.width = '210px'; + + // Add iframe + dom.add(document.body, 'iframe', {id : 'documentIframe', src : 'javascript:""', style : {display : 'none'}}); + doc = dom.get('documentIframe').contentWindow.document; + h = tinyMCEPopup.getWindowArg('head_html'); + + // Preprocess the HTML disable scripts and urls + h = h.replace(/ + + + +
      + +
      + + + + + diff --git a/web/js/globals/tinymce/plugins/iespell/editor_plugin.js b/web/js/globals/tinymce/plugins/iespell/editor_plugin.js new file mode 100644 index 0000000..e9cba10 --- /dev/null +++ b/web/js/globals/tinymce/plugins/iespell/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.IESpell",{init:function(a,b){var c=this,d;if(!tinymce.isIE){return}c.editor=a;a.addCommand("mceIESpell",function(){try{d=new ActiveXObject("ieSpell.ieSpellExtension");d.CheckDocumentNode(a.getDoc().documentElement)}catch(f){if(f.number==-2146827859){a.windowManager.confirm(a.getLang("iespell.download"),function(e){if(e){window.open("http://www.iespell.com/download.php","ieSpellDownload","")}})}else{a.windowManager.alert("Error Loading ieSpell: Exception "+f.number)}}});a.addButton("iespell",{title:"iespell.iespell_desc",cmd:"mceIESpell"})},getInfo:function(){return{longname:"IESpell (IE Only)",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/iespell",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("iespell",tinymce.plugins.IESpell)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/iespell/editor_plugin_src.js b/web/js/globals/tinymce/plugins/iespell/editor_plugin_src.js new file mode 100644 index 0000000..1b2bb98 --- /dev/null +++ b/web/js/globals/tinymce/plugins/iespell/editor_plugin_src.js @@ -0,0 +1,54 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.IESpell', { + init : function(ed, url) { + var t = this, sp; + + if (!tinymce.isIE) + return; + + t.editor = ed; + + // Register commands + ed.addCommand('mceIESpell', function() { + try { + sp = new ActiveXObject("ieSpell.ieSpellExtension"); + sp.CheckDocumentNode(ed.getDoc().documentElement); + } catch (e) { + if (e.number == -2146827859) { + ed.windowManager.confirm(ed.getLang("iespell.download"), function(s) { + if (s) + window.open('http://www.iespell.com/download.php', 'ieSpellDownload', ''); + }); + } else + ed.windowManager.alert("Error Loading ieSpell: Exception " + e.number); + } + }); + + // Register buttons + ed.addButton('iespell', {title : 'iespell.iespell_desc', cmd : 'mceIESpell'}); + }, + + getInfo : function() { + return { + longname : 'IESpell (IE Only)', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/iespell', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('iespell', tinymce.plugins.IESpell); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/inlinepopups/editor_plugin.js b/web/js/globals/tinymce/plugins/inlinepopups/editor_plugin.js new file mode 100644 index 0000000..e57c943 --- /dev/null +++ b/web/js/globals/tinymce/plugins/inlinepopups/editor_plugin.js @@ -0,0 +1 @@ +(function(){var d=tinymce.DOM,b=tinymce.dom.Element,a=tinymce.dom.Event,e=tinymce.each,c=tinymce.is;tinymce.create("tinymce.plugins.InlinePopups",{init:function(f,g){f.onBeforeRenderUI.add(function(){f.windowManager=new tinymce.InlineWindowManager(f);d.loadCSS(g+"/skins/"+(f.settings.inlinepopups_skin||"clearlooks2")+"/window.css")})},getInfo:function(){return{longname:"InlinePopups",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.create("tinymce.InlineWindowManager:tinymce.WindowManager",{InlineWindowManager:function(f){var g=this;g.parent(f);g.zIndex=300000;g.count=0;g.windows={}},open:function(r,j){var y=this,i,k="",q=y.editor,g=0,s=0,h,m,n,o,l,v,x;r=r||{};j=j||{};if(!r.inline){return y.parent(r,j)}if(!r.type){y.bookmark=q.selection.getBookmark(1)}i=d.uniqueId();h=d.getViewPort();r.width=parseInt(r.width||320);r.height=parseInt(r.height||240)+(tinymce.isIE?8:0);r.min_width=parseInt(r.min_width||150);r.min_height=parseInt(r.min_height||100);r.max_width=parseInt(r.max_width||2000);r.max_height=parseInt(r.max_height||2000);r.left=r.left||Math.round(Math.max(h.x,h.x+(h.w/2)-(r.width/2)));r.top=r.top||Math.round(Math.max(h.y,h.y+(h.h/2)-(r.height/2)));r.movable=r.resizable=true;j.mce_width=r.width;j.mce_height=r.height;j.mce_inline=true;j.mce_window_id=i;j.mce_auto_focus=r.auto_focus;y.features=r;y.params=j;y.onOpen.dispatch(y,r,j);if(r.type){k+=" mceModal";if(r.type){k+=" mce"+r.type.substring(0,1).toUpperCase()+r.type.substring(1)}r.resizable=false}if(r.statusbar){k+=" mceStatusbar"}if(r.resizable){k+=" mceResizable"}if(r.minimizable){k+=" mceMinimizable"}if(r.maximizable){k+=" mceMaximizable"}if(r.movable){k+=" mceMovable"}y._addAll(d.doc.body,["div",{id:i,"class":(q.settings.inlinepopups_skin||"clearlooks2")+(tinymce.isIE&&window.getSelection?" ie9":""),style:"width:100px;height:100px"},["div",{id:i+"_wrapper","class":"mceWrapper"+k},["div",{id:i+"_top","class":"mceTop"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_title"},r.title||""]],["div",{id:i+"_middle","class":"mceMiddle"},["div",{id:i+"_left","class":"mceLeft"}],["span",{id:i+"_content"}],["div",{id:i+"_right","class":"mceRight"}]],["div",{id:i+"_bottom","class":"mceBottom"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_status"},"Content"]],["a",{"class":"mceMove",tabindex:"-1",href:"javascript:;"}],["a",{"class":"mceMin",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMax",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMed",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceClose",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{id:i+"_resize_n","class":"mceResize mceResizeN",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_s","class":"mceResize mceResizeS",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_w","class":"mceResize mceResizeW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_e","class":"mceResize mceResizeE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_nw","class":"mceResize mceResizeNW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_ne","class":"mceResize mceResizeNE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_sw","class":"mceResize mceResizeSW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_se","class":"mceResize mceResizeSE",tabindex:"-1",href:"javascript:;"}]]]);d.setStyles(i,{top:-10000,left:-10000});if(tinymce.isGecko){d.setStyle(i,"overflow","auto")}if(!r.type){g+=d.get(i+"_left").clientWidth;g+=d.get(i+"_right").clientWidth;s+=d.get(i+"_top").clientHeight;s+=d.get(i+"_bottom").clientHeight}d.setStyles(i,{top:r.top,left:r.left,width:r.width+g,height:r.height+s});x=r.url||r.file;if(x){if(tinymce.relaxedDomain){x+=(x.indexOf("?")==-1?"?":"&")+"mce_rdomain="+tinymce.relaxedDomain}x=tinymce._addVer(x)}if(!r.type){d.add(i+"_content","iframe",{id:i+"_ifr",src:'javascript:""',frameBorder:0,style:"border:0;width:10px;height:10px"});d.setStyles(i+"_ifr",{width:r.width,height:r.height});d.setAttrib(i+"_ifr","src",x)}else{d.add(i+"_wrapper","a",{id:i+"_ok","class":"mceButton mceOk",href:"javascript:;",onmousedown:"return false;"},"Ok");if(r.type=="confirm"){d.add(i+"_wrapper","a",{"class":"mceButton mceCancel",href:"javascript:;",onmousedown:"return false;"},"Cancel")}d.add(i+"_middle","div",{"class":"mceIcon"});d.setHTML(i+"_content",r.content.replace("\n","
      "))}n=a.add(i,"mousedown",function(t){var u=t.target,f,p;f=y.windows[i];y.focus(i);if(u.nodeName=="A"||u.nodeName=="a"){if(u.className=="mceMax"){f.oldPos=f.element.getXY();f.oldSize=f.element.getSize();p=d.getViewPort();p.w-=2;p.h-=2;f.element.moveTo(p.x,p.y);f.element.resizeTo(p.w,p.h);d.setStyles(i+"_ifr",{width:p.w-f.deltaWidth,height:p.h-f.deltaHeight});d.addClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMed"){f.element.moveTo(f.oldPos.x,f.oldPos.y);f.element.resizeTo(f.oldSize.w,f.oldSize.h);f.iframeElement.resizeTo(f.oldSize.w-f.deltaWidth,f.oldSize.h-f.deltaHeight);d.removeClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMove"){return y._startDrag(i,t,u.className)}else{if(d.hasClass(u,"mceResize")){return y._startDrag(i,t,u.className.substring(13))}}}}}});o=a.add(i,"click",function(f){var p=f.target;y.focus(i);if(p.nodeName=="A"||p.nodeName=="a"){switch(p.className){case"mceClose":y.close(null,i);return a.cancel(f);case"mceButton mceOk":case"mceButton mceCancel":r.button_func(p.className=="mceButton mceOk");return a.cancel(f)}}});v=y.windows[i]={id:i,mousedown_func:n,click_func:o,element:new b(i,{blocker:1,container:q.getContainer()}),iframeElement:new b(i+"_ifr"),features:r,deltaWidth:g,deltaHeight:s};v.iframeElement.on("focus",function(){y.focus(i)});if(y.count==0&&y.editor.getParam("dialog_type","modal")=="modal"){d.add(d.doc.body,"div",{id:"mceModalBlocker","class":(y.editor.settings.inlinepopups_skin||"clearlooks2")+"_modalBlocker",style:{zIndex:y.zIndex-1}});d.show("mceModalBlocker")}else{d.setStyle("mceModalBlocker","z-index",y.zIndex-1)}if(tinymce.isIE6||/Firefox\/2\./.test(navigator.userAgent)||(tinymce.isIE&&!d.boxModel)){d.setStyles("mceModalBlocker",{position:"absolute",left:h.x,top:h.y,width:h.w-2,height:h.h-2})}y.focus(i);y._fixIELayout(i,1);if(d.get(i+"_ok")){d.get(i+"_ok").focus()}y.count++;return v},focus:function(h){var g=this,f;if(f=g.windows[h]){f.zIndex=this.zIndex++;f.element.setStyle("zIndex",f.zIndex);f.element.update();h=h+"_wrapper";d.removeClass(g.lastId,"mceFocus");d.addClass(h,"mceFocus");g.lastId=h}},_addAll:function(k,h){var g,l,f=this,j=tinymce.DOM;if(c(h,"string")){k.appendChild(j.doc.createTextNode(h))}else{if(h.length){k=k.appendChild(j.create(h[0],h[1]));for(g=2;gf){i=m;f=m.zIndex}});if(i){h.focus(i.id)}}},setTitle:function(f,g){var h;f=this._findId(f);if(h=d.get(f+"_title")){h.innerHTML=d.encode(g)}},alert:function(g,f,j){var i=this,h;h=i.open({title:i,type:"alert",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},confirm:function(g,f,j){var i=this,h;h=i.open({title:i,type:"confirm",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},_findId:function(f){var g=this;if(typeof(f)=="string"){return f}e(g.windows,function(h){var i=d.get(h.id+"_ifr");if(i&&f==i.contentWindow){f=h.id;return false}});return f},_fixIELayout:function(i,h){var f,g;if(!tinymce.isIE6){return}e(["n","s","w","e","nw","ne","sw","se"],function(j){var k=d.get(i+"_resize_"+j);d.setStyles(k,{width:h?k.clientWidth:"",height:h?k.clientHeight:"",cursor:d.getStyle(k,"cursor",1)});d.setStyle(i+"_bottom","bottom","-1px");k=0});if(f=this.windows[i]){f.element.hide();f.element.show();e(d.select("div,a",i),function(k,j){if(k.currentStyle.backgroundImage!="none"){g=new Image();g.src=k.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/,"$1")}});d.get(i).style.filter=""}}});tinymce.PluginManager.add("inlinepopups",tinymce.plugins.InlinePopups)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/inlinepopups/editor_plugin_src.js b/web/js/globals/tinymce/plugins/inlinepopups/editor_plugin_src.js new file mode 100644 index 0000000..0c1de37 --- /dev/null +++ b/web/js/globals/tinymce/plugins/inlinepopups/editor_plugin_src.js @@ -0,0 +1,635 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM, Element = tinymce.dom.Element, Event = tinymce.dom.Event, each = tinymce.each, is = tinymce.is; + + tinymce.create('tinymce.plugins.InlinePopups', { + init : function(ed, url) { + // Replace window manager + ed.onBeforeRenderUI.add(function() { + ed.windowManager = new tinymce.InlineWindowManager(ed); + DOM.loadCSS(url + '/skins/' + (ed.settings.inlinepopups_skin || 'clearlooks2') + "/window.css"); + }); + }, + + getInfo : function() { + return { + longname : 'InlinePopups', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + tinymce.create('tinymce.InlineWindowManager:tinymce.WindowManager', { + InlineWindowManager : function(ed) { + var t = this; + + t.parent(ed); + t.zIndex = 300000; + t.count = 0; + t.windows = {}; + }, + + open : function(f, p) { + var t = this, id, opt = '', ed = t.editor, dw = 0, dh = 0, vp, po, mdf, clf, we, w, u; + + f = f || {}; + p = p || {}; + + // Run native windows + if (!f.inline) + return t.parent(f, p); + + // Only store selection if the type is a normal window + if (!f.type) + t.bookmark = ed.selection.getBookmark(1); + + id = DOM.uniqueId(); + vp = DOM.getViewPort(); + f.width = parseInt(f.width || 320); + f.height = parseInt(f.height || 240) + (tinymce.isIE ? 8 : 0); + f.min_width = parseInt(f.min_width || 150); + f.min_height = parseInt(f.min_height || 100); + f.max_width = parseInt(f.max_width || 2000); + f.max_height = parseInt(f.max_height || 2000); + f.left = f.left || Math.round(Math.max(vp.x, vp.x + (vp.w / 2.0) - (f.width / 2.0))); + f.top = f.top || Math.round(Math.max(vp.y, vp.y + (vp.h / 2.0) - (f.height / 2.0))); + f.movable = f.resizable = true; + p.mce_width = f.width; + p.mce_height = f.height; + p.mce_inline = true; + p.mce_window_id = id; + p.mce_auto_focus = f.auto_focus; + + // Transpose +// po = DOM.getPos(ed.getContainer()); +// f.left -= po.x; +// f.top -= po.y; + + t.features = f; + t.params = p; + t.onOpen.dispatch(t, f, p); + + if (f.type) { + opt += ' mceModal'; + + if (f.type) + opt += ' mce' + f.type.substring(0, 1).toUpperCase() + f.type.substring(1); + + f.resizable = false; + } + + if (f.statusbar) + opt += ' mceStatusbar'; + + if (f.resizable) + opt += ' mceResizable'; + + if (f.minimizable) + opt += ' mceMinimizable'; + + if (f.maximizable) + opt += ' mceMaximizable'; + + if (f.movable) + opt += ' mceMovable'; + + // Create DOM objects + t._addAll(DOM.doc.body, + ['div', {id : id, 'class' : (ed.settings.inlinepopups_skin || 'clearlooks2') + (tinymce.isIE && window.getSelection ? ' ie9' : ''), style : 'width:100px;height:100px'}, + ['div', {id : id + '_wrapper', 'class' : 'mceWrapper' + opt}, + ['div', {id : id + '_top', 'class' : 'mceTop'}, + ['div', {'class' : 'mceLeft'}], + ['div', {'class' : 'mceCenter'}], + ['div', {'class' : 'mceRight'}], + ['span', {id : id + '_title'}, f.title || ''] + ], + + ['div', {id : id + '_middle', 'class' : 'mceMiddle'}, + ['div', {id : id + '_left', 'class' : 'mceLeft'}], + ['span', {id : id + '_content'}], + ['div', {id : id + '_right', 'class' : 'mceRight'}] + ], + + ['div', {id : id + '_bottom', 'class' : 'mceBottom'}, + ['div', {'class' : 'mceLeft'}], + ['div', {'class' : 'mceCenter'}], + ['div', {'class' : 'mceRight'}], + ['span', {id : id + '_status'}, 'Content'] + ], + + ['a', {'class' : 'mceMove', tabindex : '-1', href : 'javascript:;'}], + ['a', {'class' : 'mceMin', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceMax', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceMed', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceClose', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {id : id + '_resize_n', 'class' : 'mceResize mceResizeN', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_s', 'class' : 'mceResize mceResizeS', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_w', 'class' : 'mceResize mceResizeW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_e', 'class' : 'mceResize mceResizeE', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_nw', 'class' : 'mceResize mceResizeNW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_ne', 'class' : 'mceResize mceResizeNE', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_sw', 'class' : 'mceResize mceResizeSW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_se', 'class' : 'mceResize mceResizeSE', tabindex : '-1', href : 'javascript:;'}] + ] + ] + ); + + DOM.setStyles(id, {top : -10000, left : -10000}); + + // Fix gecko rendering bug, where the editors iframe messed with window contents + if (tinymce.isGecko) + DOM.setStyle(id, 'overflow', 'auto'); + + // Measure borders + if (!f.type) { + dw += DOM.get(id + '_left').clientWidth; + dw += DOM.get(id + '_right').clientWidth; + dh += DOM.get(id + '_top').clientHeight; + dh += DOM.get(id + '_bottom').clientHeight; + } + + // Resize window + DOM.setStyles(id, {top : f.top, left : f.left, width : f.width + dw, height : f.height + dh}); + + u = f.url || f.file; + if (u) { + if (tinymce.relaxedDomain) + u += (u.indexOf('?') == -1 ? '?' : '&') + 'mce_rdomain=' + tinymce.relaxedDomain; + + u = tinymce._addVer(u); + } + + if (!f.type) { + DOM.add(id + '_content', 'iframe', {id : id + '_ifr', src : 'javascript:""', frameBorder : 0, style : 'border:0;width:10px;height:10px'}); + DOM.setStyles(id + '_ifr', {width : f.width, height : f.height}); + DOM.setAttrib(id + '_ifr', 'src', u); + } else { + DOM.add(id + '_wrapper', 'a', {id : id + '_ok', 'class' : 'mceButton mceOk', href : 'javascript:;', onmousedown : 'return false;'}, 'Ok'); + + if (f.type == 'confirm') + DOM.add(id + '_wrapper', 'a', {'class' : 'mceButton mceCancel', href : 'javascript:;', onmousedown : 'return false;'}, 'Cancel'); + + DOM.add(id + '_middle', 'div', {'class' : 'mceIcon'}); + DOM.setHTML(id + '_content', f.content.replace('\n', '
      ')); + } + + // Register events + mdf = Event.add(id, 'mousedown', function(e) { + var n = e.target, w, vp; + + w = t.windows[id]; + t.focus(id); + + if (n.nodeName == 'A' || n.nodeName == 'a') { + if (n.className == 'mceMax') { + w.oldPos = w.element.getXY(); + w.oldSize = w.element.getSize(); + + vp = DOM.getViewPort(); + + // Reduce viewport size to avoid scrollbars + vp.w -= 2; + vp.h -= 2; + + w.element.moveTo(vp.x, vp.y); + w.element.resizeTo(vp.w, vp.h); + DOM.setStyles(id + '_ifr', {width : vp.w - w.deltaWidth, height : vp.h - w.deltaHeight}); + DOM.addClass(id + '_wrapper', 'mceMaximized'); + } else if (n.className == 'mceMed') { + // Reset to old size + w.element.moveTo(w.oldPos.x, w.oldPos.y); + w.element.resizeTo(w.oldSize.w, w.oldSize.h); + w.iframeElement.resizeTo(w.oldSize.w - w.deltaWidth, w.oldSize.h - w.deltaHeight); + + DOM.removeClass(id + '_wrapper', 'mceMaximized'); + } else if (n.className == 'mceMove') + return t._startDrag(id, e, n.className); + else if (DOM.hasClass(n, 'mceResize')) + return t._startDrag(id, e, n.className.substring(13)); + } + }); + + clf = Event.add(id, 'click', function(e) { + var n = e.target; + + t.focus(id); + + if (n.nodeName == 'A' || n.nodeName == 'a') { + switch (n.className) { + case 'mceClose': + t.close(null, id); + return Event.cancel(e); + + case 'mceButton mceOk': + case 'mceButton mceCancel': + f.button_func(n.className == 'mceButton mceOk'); + return Event.cancel(e); + } + } + }); + + // Add window + w = t.windows[id] = { + id : id, + mousedown_func : mdf, + click_func : clf, + element : new Element(id, {blocker : 1, container : ed.getContainer()}), + iframeElement : new Element(id + '_ifr'), + features : f, + deltaWidth : dw, + deltaHeight : dh + }; + + w.iframeElement.on('focus', function() { + t.focus(id); + }); + + // Setup blocker + if (t.count == 0 && t.editor.getParam('dialog_type', 'modal') == 'modal') { + DOM.add(DOM.doc.body, 'div', { + id : 'mceModalBlocker', + 'class' : (t.editor.settings.inlinepopups_skin || 'clearlooks2') + '_modalBlocker', + style : {zIndex : t.zIndex - 1} + }); + + DOM.show('mceModalBlocker'); // Reduces flicker in IE + } else + DOM.setStyle('mceModalBlocker', 'z-index', t.zIndex - 1); + + if (tinymce.isIE6 || /Firefox\/2\./.test(navigator.userAgent) || (tinymce.isIE && !DOM.boxModel)) + DOM.setStyles('mceModalBlocker', {position : 'absolute', left : vp.x, top : vp.y, width : vp.w - 2, height : vp.h - 2}); + + t.focus(id); + t._fixIELayout(id, 1); + + // Focus ok button + if (DOM.get(id + '_ok')) + DOM.get(id + '_ok').focus(); + + t.count++; + + return w; + }, + + focus : function(id) { + var t = this, w; + + if (w = t.windows[id]) { + w.zIndex = this.zIndex++; + w.element.setStyle('zIndex', w.zIndex); + w.element.update(); + + id = id + '_wrapper'; + DOM.removeClass(t.lastId, 'mceFocus'); + DOM.addClass(id, 'mceFocus'); + t.lastId = id; + } + }, + + _addAll : function(te, ne) { + var i, n, t = this, dom = tinymce.DOM; + + if (is(ne, 'string')) + te.appendChild(dom.doc.createTextNode(ne)); + else if (ne.length) { + te = te.appendChild(dom.create(ne[0], ne[1])); + + for (i=2; i ix) { + fw = w; + ix = w.zIndex; + } + }); + + if (fw) + t.focus(fw.id); + } + }, + + setTitle : function(w, ti) { + var e; + + w = this._findId(w); + + if (e = DOM.get(w + '_title')) + e.innerHTML = DOM.encode(ti); + }, + + alert : function(txt, cb, s) { + var t = this, w; + + w = t.open({ + title : t, + type : 'alert', + button_func : function(s) { + if (cb) + cb.call(s || t, s); + + t.close(null, w.id); + }, + content : DOM.encode(t.editor.getLang(txt, txt)), + inline : 1, + width : 400, + height : 130 + }); + }, + + confirm : function(txt, cb, s) { + var t = this, w; + + w = t.open({ + title : t, + type : 'confirm', + button_func : function(s) { + if (cb) + cb.call(s || t, s); + + t.close(null, w.id); + }, + content : DOM.encode(t.editor.getLang(txt, txt)), + inline : 1, + width : 400, + height : 130 + }); + }, + + // Internal functions + + _findId : function(w) { + var t = this; + + if (typeof(w) == 'string') + return w; + + each(t.windows, function(wo) { + var ifr = DOM.get(wo.id + '_ifr'); + + if (ifr && w == ifr.contentWindow) { + w = wo.id; + return false; + } + }); + + return w; + }, + + _fixIELayout : function(id, s) { + var w, img; + + if (!tinymce.isIE6) + return; + + // Fixes the bug where hover flickers and does odd things in IE6 + each(['n','s','w','e','nw','ne','sw','se'], function(v) { + var e = DOM.get(id + '_resize_' + v); + + DOM.setStyles(e, { + width : s ? e.clientWidth : '', + height : s ? e.clientHeight : '', + cursor : DOM.getStyle(e, 'cursor', 1) + }); + + DOM.setStyle(id + "_bottom", 'bottom', '-1px'); + + e = 0; + }); + + // Fixes graphics glitch + if (w = this.windows[id]) { + // Fixes rendering bug after resize + w.element.hide(); + w.element.show(); + + // Forced a repaint of the window + //DOM.get(id).style.filter = ''; + + // IE has a bug where images used in CSS won't get loaded + // sometimes when the cache in the browser is disabled + // This fix tries to solve it by loading the images using the image object + each(DOM.select('div,a', id), function(e, i) { + if (e.currentStyle.backgroundImage != 'none') { + img = new Image(); + img.src = e.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/, '$1'); + } + }); + + DOM.get(id).style.filter = ''; + } + } + }); + + // Register plugin + tinymce.PluginManager.add('inlinepopups', tinymce.plugins.InlinePopups); +})(); + diff --git a/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif b/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif new file mode 100644 index 0000000000000000000000000000000000000000..94abd08763fffdaa0dd5c5afb470a97294f2b94d GIT binary patch literal 818 zcmV-21I_$LNk%w1VITk?0OkMyy?1uhZf>Is3*B5?sT&&Hqoc$;Jkrt6&k+&QHa5gV zL)l77I5;@fLqpYMWV+*+oUyj*ia`4%)P|vrSClaB!?EE7K$--(_XZ zOH0lO2-#9n!;Fik78a-!6wR}-yS%#378ch%J=j4(x@2V5*3{b0&C|=t(mFcQDJi8A z60bTsucxKY8XD3{O5bW~+gDfHP*AZbD54S)*gHGL#>A(co5`c08yg$Yzr7_TCCA9d zs-U0MFfhzxW4%d9s-K?K($LSkxy-Sz(7?Xdn3%wSe#oDmxL#e?qN1My0^C(q&nzt3 zjEvWFbJcly)5*uu)6w93eACp@*{!X#QBc56PRYv1%goBm&CA4*kj9vnyFxN00960|JK*lA^8LV00000EC2ui z03ZM$000O7fPaF6goTEOh>41ejE#5-A-Y zDMkRMg$FSdD>XGe76Lo4g8*}CUeivLI}B6rYIE)9Vh306CXDUKb=Dfx`}wT=u<6# zD$n)U&_b6YEgl901IUC4zyf`27&(S$$E;fb{Wx)wm4^u-0H zv*CdXLINn%=tH`+>C>qDxJcmTfS@*Z45S!AI|Ya#EOHOnP2`2|1;88Ju#tey5e=^) w9O1*fx%21Hn;(HtX3!cMK%gku&b_<$??7lEkM$&p`Sa-0r)T3DnWI1eJD_KQDgXcg literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/button.gif b/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/button.gif new file mode 100644 index 0000000000000000000000000000000000000000..e671094cb0eb210b756117f992cf5ca2caa698fd GIT binary patch literal 280 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 literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/horizontal.gif b/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/horizontal.gif new file mode 100644 index 0000000000000000000000000000000000000000..c2a2ad454db194e428a7b9da40f62d5376a17428 GIT binary patch literal 769 zcmb7?=`WiB0LI^D)(HC`v6*aD4AW#LGRb0Oi$tH~CP=nb%N%8n zy02ceuewTUUDeW|RaMp*TlLV6R$o=rQOY{@AME+?`}QQiCwU%4Jq)?`{5B8=ECT1T z+wH!7{zji$)-UA>D9({)xO1SJGLHK54Mat(}s4_unMiKjB85EHng*~!Ddt+?(c5uHG4ZI zsc>jP#5Y6w6Wj5!Y=0^oK*&D+R;Yh@ze z7vfi;qFW{owiOfGqcB@XkwUZ0j?Km4{qjE- z6c!Z|O1!?5l~+^}tE#*^aCo0?lZ$rLKBwT;dI+nLO(UEMvb-ad9elEWPw8Xg(t zx$y<#6T+{PQ@$ecjAT|iC%dxnP5yoH$I`OLFU5*drPiz>bidcu^@a^2v}rP3-`?4^ z?Cl>M-Z(n8ot*x$0~Z|;ku35!-qAHSQN*GM3tW8AN#VWJNrHQD+6qXfO_zB^6eFVU zOjzupAb0*`W8} zQVeE5Djt<a0+Owme6r2OGio7DoTWqkhGKj0`0*1-*<$#uL5YH*kC8Z>wpCvYO~asp;G r-~A;>$wp)vkltB=c_?k6Zw*FUgrbAm;sB08O9+}m=}H3OFd*zN8L+JA literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif b/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif new file mode 100644 index 0000000000000000000000000000000000000000..43a735f22c81d6d7d99c1ba9f034f38bfdd1a92b GIT binary patch literal 92 zcmZ?wbhEHb&D4o4FLHO9PR)B literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/window.css b/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/window.css new file mode 100644 index 0000000..202a853 --- /dev/null +++ b/web/js/globals/tinymce/plugins/inlinepopups/skins/clearlooks2/window.css @@ -0,0 +1,100 @@ +/* Clearlooks 2 */ + +/* Reset */ +.clearlooks2, .clearlooks2 div, .clearlooks2 span, .clearlooks2 a {vertical-align:baseline; text-align:left; position:absolute; border:0; padding:0; margin:0; background:transparent; font-family:Arial,Verdana; font-size:11px; color:#000; text-decoration:none; font-weight:normal; width:auto; height:auto; overflow:hidden; display:block} + +/* General */ +.clearlooks2 {position:absolute; direction:ltr} +.clearlooks2 .mceWrapper {position:static} +.mceEventBlocker {position:fixed; left:0; top:0; background:url(img/horizontal.gif) no-repeat 0 -75px; width:100%; height:100%} +.clearlooks2 .mcePlaceHolder {border:1px solid #000; background:#888; top:0; left:0; opacity:0.5; -ms-filter:'alpha(opacity=50)'; filter:alpha(opacity=50)} +.clearlooks2_modalBlocker {position:fixed; left:0; top:0; width:100%; height:100%; background:#FFF; opacity:0.6; -ms-filter:'alpha(opacity=60)'; filter:alpha(opacity=60); display:none} + +/* Top */ +.clearlooks2 .mceTop, .clearlooks2 .mceTop div {top:0; width:100%; height:23px} +.clearlooks2 .mceTop .mceLeft {width:6px; background:url(img/corners.gif)} +.clearlooks2 .mceTop .mceCenter {right:6px; width:100%; height:23px; background:url(img/horizontal.gif) 12px 0; clip:rect(auto auto auto 12px)} +.clearlooks2 .mceTop .mceRight {right:0; width:6px; height:23px; background:url(img/corners.gif) -12px 0} +.clearlooks2 .mceTop span {width:100%; text-align:center; vertical-align:middle; line-height:23px; font-weight:bold} +.clearlooks2 .mceFocus .mceTop .mceLeft {background:url(img/corners.gif) -6px 0} +.clearlooks2 .mceFocus .mceTop .mceCenter {background:url(img/horizontal.gif) 0 -23px} +.clearlooks2 .mceFocus .mceTop .mceRight {background:url(img/corners.gif) -18px 0} +.clearlooks2 .mceFocus .mceTop span {color:#FFF} + +/* Middle */ +.clearlooks2 .mceMiddle, .clearlooks2 .mceMiddle div {top:0} +.clearlooks2 .mceMiddle {width:100%; height:100%; clip:rect(23px auto auto auto)} +.clearlooks2 .mceMiddle .mceLeft {left:0; width:5px; height:100%; background:url(img/vertical.gif) -5px 0} +.clearlooks2 .mceMiddle span {top:23px; left:5px; width:100%; height:100%; background:#FFF} +.clearlooks2 .mceMiddle .mceRight {right:0; width:5px; height:100%; background:url(img/vertical.gif)} + +/* Bottom */ +.clearlooks2 .mceBottom, .clearlooks2 .mceBottom div {height:6px} +.clearlooks2 .mceBottom {left:0; bottom:0; width:100%} +.clearlooks2 .mceBottom div {top:0} +.clearlooks2 .mceBottom .mceLeft {left:0; width:5px; background:url(img/corners.gif) -34px -6px} +.clearlooks2 .mceBottom .mceCenter {left:5px; width:100%; background:url(img/horizontal.gif) 0 -46px} +.clearlooks2 .mceBottom .mceRight {right:0; width:5px; background: url(img/corners.gif) -34px 0} +.clearlooks2 .mceBottom span {display:none} +.clearlooks2 .mceStatusbar .mceBottom, .clearlooks2 .mceStatusbar .mceBottom div {height:23px} +.clearlooks2 .mceStatusbar .mceBottom .mceLeft {background:url(img/corners.gif) -29px 0} +.clearlooks2 .mceStatusbar .mceBottom .mceCenter {background:url(img/horizontal.gif) 0 -52px} +.clearlooks2 .mceStatusbar .mceBottom .mceRight {background:url(img/corners.gif) -24px 0} +.clearlooks2 .mceStatusbar .mceBottom span {display:block; left:7px; font-family:Arial, Verdana; font-size:11px; line-height:23px} + +/* Actions */ +.clearlooks2 a {width:29px; height:16px; top:3px;} +.clearlooks2 .mceClose {right:6px; background:url(img/buttons.gif) -87px 0} +.clearlooks2 .mceMin {display:none; right:68px; background:url(img/buttons.gif) 0 0} +.clearlooks2 .mceMed {display:none; right:37px; background:url(img/buttons.gif) -29px 0} +.clearlooks2 .mceMax {display:none; right:37px; background:url(img/buttons.gif) -58px 0} +.clearlooks2 .mceMove {display:none;width:100%;cursor:move;background:url(img/corners.gif) no-repeat -100px -100px} +.clearlooks2 .mceMovable .mceMove {display:block} +.clearlooks2 .mceFocus .mceClose {right:6px; background:url(img/buttons.gif) -87px -16px} +.clearlooks2 .mceFocus .mceMin {right:68px; background:url(img/buttons.gif) 0 -16px} +.clearlooks2 .mceFocus .mceMed {right:37px; background:url(img/buttons.gif) -29px -16px} +.clearlooks2 .mceFocus .mceMax {right:37px; background:url(img/buttons.gif) -58px -16px} +.clearlooks2 .mceFocus .mceClose:hover {right:6px; background:url(img/buttons.gif) -87px -32px} +.clearlooks2 .mceFocus .mceClose:hover {right:6px; background:url(img/buttons.gif) -87px -32px} +.clearlooks2 .mceFocus .mceMin:hover {right:68px; background:url(img/buttons.gif) 0 -32px} +.clearlooks2 .mceFocus .mceMed:hover {right:37px; background:url(img/buttons.gif) -29px -32px} +.clearlooks2 .mceFocus .mceMax:hover {right:37px; background:url(img/buttons.gif) -58px -32px} + +/* Resize */ +.clearlooks2 .mceResize {top:auto; left:auto; display:none; width:5px; height:5px; background:url(img/horizontal.gif) no-repeat 0 -75px} +.clearlooks2 .mceResizable .mceResize {display:block} +.clearlooks2 .mceResizable .mceMin, .clearlooks2 .mceMax {display:none} +.clearlooks2 .mceMinimizable .mceMin {display:block} +.clearlooks2 .mceMaximizable .mceMax {display:block} +.clearlooks2 .mceMaximized .mceMed {display:block} +.clearlooks2 .mceMaximized .mceMax {display:none} +.clearlooks2 a.mceResizeN {top:0; left:0; width:100%; cursor:n-resize} +.clearlooks2 a.mceResizeNW {top:0; left:0; cursor:nw-resize} +.clearlooks2 a.mceResizeNE {top:0; right:0; cursor:ne-resize} +.clearlooks2 a.mceResizeW {top:0; left:0; height:100%; cursor:w-resize;} +.clearlooks2 a.mceResizeE {top:0; right:0; height:100%; cursor:e-resize} +.clearlooks2 a.mceResizeS {bottom:0; left:0; width:100%; cursor:s-resize} +.clearlooks2 a.mceResizeSW {bottom:0; left:0; cursor:sw-resize} +.clearlooks2 a.mceResizeSE {bottom:0; right:0; cursor:se-resize} + +/* Alert/Confirm */ +.clearlooks2 .mceButton {font-weight:bold; bottom:10px; width:80px; height:30px; background:url(img/button.gif); line-height:30px; vertical-align:middle; text-align:center; outline:0} +.clearlooks2 .mceMiddle .mceIcon {left:15px; top:35px; width:32px; height:32px} +.clearlooks2 .mceAlert .mceMiddle span, .clearlooks2 .mceConfirm .mceMiddle span {background:transparent;left:60px; top:35px; width:320px; height:50px; font-weight:bold; overflow:auto; white-space:normal} +.clearlooks2 a:hover {font-weight:bold;} +.clearlooks2 .mceAlert .mceMiddle, .clearlooks2 .mceConfirm .mceMiddle {background:#D6D7D5} +.clearlooks2 .mceAlert .mceOk {left:50%; top:auto; margin-left: -40px} +.clearlooks2 .mceAlert .mceIcon {background:url(img/alert.gif)} +.clearlooks2 .mceConfirm .mceOk {left:50%; top:auto; margin-left: -90px} +.clearlooks2 .mceConfirm .mceCancel {left:50%; top:auto} +.clearlooks2 .mceConfirm .mceIcon {background:url(img/confirm.gif)} + +/* IE9 fixes */ +.clearlooks2.ie9 .mceTop .mceCenter {clip:auto;} +.clearlooks2.ie9 .mceMiddle {clip:auto;} +.clearlooks2.ie9 .mceMiddle .mceLeft, .clearlooks2.ie9 .mceMiddle .mceRight {top: 23px;} +.clearlooks2.ie9 .mceAlert .mceMiddle span, .clearlooks2 .mceConfirm .mceMiddle span {top:13px;} +.clearlooks2.ie9 .mceModal .mceMiddle {top:23px} +.clearlooks2.ie9 .mceModal .mceMiddle .mceLeft, .clearlooks2.ie9 .mceModal .mceMiddle .mceRight {top: 0} +.clearlooks2.ie9 .mceMiddle .mceIcon {top:13px} +.clearlooks2.ie9 .mceTop .mceCenter {top:0; right:auto; left:6px; width:calc(100%-12px)} diff --git a/web/js/globals/tinymce/plugins/inlinepopups/template.htm b/web/js/globals/tinymce/plugins/inlinepopups/template.htm new file mode 100644 index 0000000..f9ec642 --- /dev/null +++ b/web/js/globals/tinymce/plugins/inlinepopups/template.htm @@ -0,0 +1,387 @@ + + + +Template for dialogs + + + + +
      +
      +
      +
      +
      +
      +
      + Blured +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Focused +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Statusbar +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Statusbar, Resizable +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Resizable, Maximizable +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Blurred, Maximizable, Statusbar, Resizable +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Maximized, Maximizable, Minimizable +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Blured +
      + +
      +
      + Content +
      +
      + +
      +
      +
      +
      + Statusbar text. +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      +
      +
      + Alert +
      + +
      +
      + + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + +
      +
      +
      + +
      +
      +
      +
      +
      + + + Ok + +
      +
      + +
      +
      +
      +
      +
      +
      + Confirm +
      + +
      +
      + + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + This is a very long error message. This is a very long error message. + +
      +
      +
      + +
      +
      +
      +
      +
      + + + Ok + Cancel + +
      +
      +
      + + + diff --git a/web/js/globals/tinymce/plugins/insertdatetime/editor_plugin.js b/web/js/globals/tinymce/plugins/insertdatetime/editor_plugin.js new file mode 100644 index 0000000..938ce6b --- /dev/null +++ b/web/js/globals/tinymce/plugins/insertdatetime/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.InsertDateTime",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceInsertDate",function(){var d=c._getDateTime(new Date(),a.getParam("plugin_insertdate_dateFormat",a.getLang("insertdatetime.date_fmt")));a.execCommand("mceInsertContent",false,d)});a.addCommand("mceInsertTime",function(){var d=c._getDateTime(new Date(),a.getParam("plugin_insertdate_timeFormat",a.getLang("insertdatetime.time_fmt")));a.execCommand("mceInsertContent",false,d)});a.addButton("insertdate",{title:"insertdatetime.insertdate_desc",cmd:"mceInsertDate"});a.addButton("inserttime",{title:"insertdatetime.inserttime_desc",cmd:"mceInsertTime"})},getInfo:function(){return{longname:"Insert date/time",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/insertdatetime",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_getDateTime:function(e,a){var c=this.editor;function b(g,d){g=""+g;if(g.length-1){a[c].style.zIndex=g[j];a[j].style.zIndex=g[c]}else{if(g[c]>0){a[c].style.zIndex=g[c]-1}}}else{for(f=0;fg[c]){j=f;break}}if(j>-1){a[c].style.zIndex=g[j];a[j].style.zIndex=g[c]}else{a[c].style.zIndex=g[c]+1}}b.execCommand("mceRepaint")},_getParentLayer:function(a){return this.editor.dom.getParent(a,function(b){return b.nodeType==1&&/^(absolute|relative|static)$/i.test(b.style.position)})},_insertLayer:function(){var a=this.editor,b=a.dom.getPos(a.dom.getParent(a.selection.getNode(),"*"));a.dom.add(a.getBody(),"div",{style:{position:"absolute",left:b.x,top:(b.y>20?b.y:20),width:100,height:100},"class":"mceItemVisualAid"},a.selection.getContent()||a.getLang("layer.content"))},_toggleAbsolute:function(){var a=this.editor,b=this._getParentLayer(a.selection.getNode());if(!b){b=a.dom.getParent(a.selection.getNode(),"DIV,P,IMG")}if(b){if(b.style.position.toLowerCase()=="absolute"){a.dom.setStyles(b,{position:"",left:"",top:"",width:"",height:""});a.dom.removeClass(b,"mceItemVisualAid")}else{if(b.style.left==""){b.style.left=20+"px"}if(b.style.top==""){b.style.top=20+"px"}if(b.style.width==""){b.style.width=b.width?(b.width+"px"):"100px"}if(b.style.height==""){b.style.height=b.height?(b.height+"px"):"100px"}b.style.position="absolute";a.addVisual(a.getBody())}a.execCommand("mceRepaint");a.nodeChanged()}}});tinymce.PluginManager.add("layer",tinymce.plugins.Layer)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/layer/editor_plugin_src.js b/web/js/globals/tinymce/plugins/layer/editor_plugin_src.js new file mode 100644 index 0000000..d5aa865 --- /dev/null +++ b/web/js/globals/tinymce/plugins/layer/editor_plugin_src.js @@ -0,0 +1,212 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Layer', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceInsertLayer', t._insertLayer, t); + + ed.addCommand('mceMoveForward', function() { + t._move(1); + }); + + ed.addCommand('mceMoveBackward', function() { + t._move(-1); + }); + + ed.addCommand('mceMakeAbsolute', function() { + t._toggleAbsolute(); + }); + + // Register buttons + ed.addButton('moveforward', {title : 'layer.forward_desc', cmd : 'mceMoveForward'}); + ed.addButton('movebackward', {title : 'layer.backward_desc', cmd : 'mceMoveBackward'}); + ed.addButton('absolute', {title : 'layer.absolute_desc', cmd : 'mceMakeAbsolute'}); + ed.addButton('insertlayer', {title : 'layer.insertlayer_desc', cmd : 'mceInsertLayer'}); + + ed.onInit.add(function() { + if (tinymce.isIE) + ed.getDoc().execCommand('2D-Position', false, true); + }); + + ed.onNodeChange.add(t._nodeChange, t); + ed.onVisualAid.add(t._visualAid, t); + }, + + getInfo : function() { + return { + longname : 'Layer', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + _nodeChange : function(ed, cm, n) { + var le, p; + + le = this._getParentLayer(n); + p = ed.dom.getParent(n, 'DIV,P,IMG'); + + if (!p) { + cm.setDisabled('absolute', 1); + cm.setDisabled('moveforward', 1); + cm.setDisabled('movebackward', 1); + } else { + cm.setDisabled('absolute', 0); + cm.setDisabled('moveforward', !le); + cm.setDisabled('movebackward', !le); + cm.setActive('absolute', le && le.style.position.toLowerCase() == "absolute"); + } + }, + + // Private methods + + _visualAid : function(ed, e, s) { + var dom = ed.dom; + + tinymce.each(dom.select('div,p', e), function(e) { + if (/^(absolute|relative|static)$/i.test(e.style.position)) { + if (s) + dom.addClass(e, 'mceItemVisualAid'); + else + dom.removeClass(e, 'mceItemVisualAid'); + } + }); + }, + + _move : function(d) { + var ed = this.editor, i, z = [], le = this._getParentLayer(ed.selection.getNode()), ci = -1, fi = -1, nl; + + nl = []; + tinymce.walk(ed.getBody(), function(n) { + if (n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position)) + nl.push(n); + }, 'childNodes'); + + // Find z-indexes + for (i=0; i -1) { + nl[ci].style.zIndex = z[fi]; + nl[fi].style.zIndex = z[ci]; + } else { + if (z[ci] > 0) + nl[ci].style.zIndex = z[ci] - 1; + } + } else { + // Move forward + + // Try find a higher one + for (i=0; i z[ci]) { + fi = i; + break; + } + } + + if (fi > -1) { + nl[ci].style.zIndex = z[fi]; + nl[fi].style.zIndex = z[ci]; + } else + nl[ci].style.zIndex = z[ci] + 1; + } + + ed.execCommand('mceRepaint'); + }, + + _getParentLayer : function(n) { + return this.editor.dom.getParent(n, function(n) { + return n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position); + }); + }, + + _insertLayer : function() { + var ed = this.editor, p = ed.dom.getPos(ed.dom.getParent(ed.selection.getNode(), '*')); + + ed.dom.add(ed.getBody(), 'div', { + style : { + position : 'absolute', + left : p.x, + top : (p.y > 20 ? p.y : 20), + width : 100, + height : 100 + }, + 'class' : 'mceItemVisualAid' + }, ed.selection.getContent() || ed.getLang('layer.content')); + }, + + _toggleAbsolute : function() { + var ed = this.editor, le = this._getParentLayer(ed.selection.getNode()); + + if (!le) + le = ed.dom.getParent(ed.selection.getNode(), 'DIV,P,IMG'); + + if (le) { + if (le.style.position.toLowerCase() == "absolute") { + ed.dom.setStyles(le, { + position : '', + left : '', + top : '', + width : '', + height : '' + }); + + ed.dom.removeClass(le, 'mceItemVisualAid'); + } else { + if (le.style.left == "") + le.style.left = 20 + 'px'; + + if (le.style.top == "") + le.style.top = 20 + 'px'; + + if (le.style.width == "") + le.style.width = le.width ? (le.width + 'px') : '100px'; + + if (le.style.height == "") + le.style.height = le.height ? (le.height + 'px') : '100px'; + + le.style.position = "absolute"; + ed.addVisual(ed.getBody()); + } + + ed.execCommand('mceRepaint'); + ed.nodeChanged(); + } + } + }); + + // Register plugin + tinymce.PluginManager.add('layer', tinymce.plugins.Layer); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/legacyoutput/editor_plugin.js b/web/js/globals/tinymce/plugins/legacyoutput/editor_plugin.js new file mode 100644 index 0000000..29d43c5 --- /dev/null +++ b/web/js/globals/tinymce/plugins/legacyoutput/editor_plugin.js @@ -0,0 +1 @@ +(function(a){a.onAddEditor.addToTop(function(c,b){b.settings.inline_styles=false});a.create("tinymce.plugins.LegacyOutput",{init:function(b){b.onInit.add(function(){var c="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",e=a.explode(b.settings.font_size_style_values),d=b.serializer;b.formatter.register({alignleft:{selector:c,attributes:{align:"left"}},aligncenter:{selector:c,attributes:{align:"center"}},alignright:{selector:c,attributes:{align:"right"}},alignfull:{selector:c,attributes:{align:"full"}},bold:{inline:"b"},italic:{inline:"i"},underline:{inline:"u"},strikethrough:{inline:"strike"},fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(f){return a.inArray(e,f.value)+1}}},forecolor:{inline:"font",styles:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}}});d._setup();a.each("b,i,u,strike".split(","),function(f){var g=d.rules[f];if(!g){d.addRules(f)}});if(!d.rules.font){d.addRules("font[face|size|color|style]")}a.each(c.split(","),function(f){var h=d.rules[f],g;if(h){a.each(h.attribs,function(j,i){if(i.name=="align"){g=true;return false}});if(!g){h.attribs.push({name:"align"})}}});b.onNodeChange.add(function(g,k){var j,f,h,i;f=g.dom.getParent(g.selection.getNode(),"font");if(f){h=f.face;i=f.size}if(j=k.get("fontselect")){j.select(function(l){return l==h})}if(j=k.get("fontsizeselect")){j.select(function(m){var l=a.inArray(e,m.fontSize);return l+1==i})}})})},getInfo:function(){return{longname:"LegacyOutput",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/legacyoutput",version:a.majorVersion+"."+a.minorVersion}}});a.PluginManager.add("legacyoutput",a.plugins.LegacyOutput)})(tinymce); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/legacyoutput/editor_plugin_src.js b/web/js/globals/tinymce/plugins/legacyoutput/editor_plugin_src.js new file mode 100644 index 0000000..e852da1 --- /dev/null +++ b/web/js/globals/tinymce/plugins/legacyoutput/editor_plugin_src.js @@ -0,0 +1,136 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + * + * This plugin will force TinyMCE to produce deprecated legacy output such as font elements, u elements, align + * attributes and so forth. There are a few cases where these old items might be needed for example in email applications or with Flash + * + * However you should NOT use this plugin if you are building some system that produces web contents such as a CMS. All these elements are + * not apart of the newer specifications for HTML and XHTML. + */ + +(function(tinymce) { + // Override inline_styles setting to force TinyMCE to produce deprecated contents + tinymce.onAddEditor.addToTop(function(tinymce, editor) { + editor.settings.inline_styles = false; + }); + + // Create the legacy ouput plugin + tinymce.create('tinymce.plugins.LegacyOutput', { + init : function(editor) { + editor.onInit.add(function() { + var alignElements = 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', + fontSizes = tinymce.explode(editor.settings.font_size_style_values), + serializer = editor.serializer; + + // Override some internal formats to produce legacy elements and attributes + editor.formatter.register({ + // Change alignment formats to use the deprecated align attribute + alignleft : {selector : alignElements, attributes : {align : 'left'}}, + aligncenter : {selector : alignElements, attributes : {align : 'center'}}, + alignright : {selector : alignElements, attributes : {align : 'right'}}, + alignfull : {selector : alignElements, attributes : {align : 'full'}}, + + // Change the basic formatting elements to use deprecated element types + bold : {inline : 'b'}, + italic : {inline : 'i'}, + underline : {inline : 'u'}, + strikethrough : {inline : 'strike'}, + + // Change font size and font family to use the deprecated font element + fontname : {inline : 'font', attributes : {face : '%value'}}, + fontsize : { + inline : 'font', + attributes : { + size : function(vars) { + return tinymce.inArray(fontSizes, vars.value) + 1; + } + } + }, + + // Setup font elements for colors as well + forecolor : {inline : 'font', styles : {color : '%value'}}, + hilitecolor : {inline : 'font', styles : {backgroundColor : '%value'}} + }); + + // Force parsing of the serializer rules + serializer._setup(); + + // Check that deprecated elements are allowed if not add them + tinymce.each('b,i,u,strike'.split(','), function(name) { + var rule = serializer.rules[name]; + + if (!rule) + serializer.addRules(name); + }); + + // Add font element if it's missing + if (!serializer.rules["font"]) + serializer.addRules("font[face|size|color|style]"); + + // Add the missing and depreacted align attribute for the serialization engine + tinymce.each(alignElements.split(','), function(name) { + var rule = serializer.rules[name], found; + + if (rule) { + tinymce.each(rule.attribs, function(name, attr) { + if (attr.name == 'align') { + found = true; + return false; + } + }); + + if (!found) + rule.attribs.push({name : 'align'}); + } + }); + + // Listen for the onNodeChange event so that we can do special logic for the font size and font name drop boxes + editor.onNodeChange.add(function(editor, control_manager) { + var control, fontElm, fontName, fontSize; + + // Find font element get it's name and size + fontElm = editor.dom.getParent(editor.selection.getNode(), 'font'); + if (fontElm) { + fontName = fontElm.face; + fontSize = fontElm.size; + } + + // Select/unselect the font name in droplist + if (control = control_manager.get('fontselect')) { + control.select(function(value) { + return value == fontName; + }); + } + + // Select/unselect the font size in droplist + if (control = control_manager.get('fontsizeselect')) { + control.select(function(value) { + var index = tinymce.inArray(fontSizes, value.fontSize); + + return index + 1 == fontSize; + }); + } + }); + }); + }, + + getInfo : function() { + return { + longname : 'LegacyOutput', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/legacyoutput', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('legacyoutput', tinymce.plugins.LegacyOutput); +})(tinymce); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/media/css/content.css b/web/js/globals/tinymce/plugins/media/css/content.css new file mode 100644 index 0000000..1bf6a75 --- /dev/null +++ b/web/js/globals/tinymce/plugins/media/css/content.css @@ -0,0 +1,6 @@ +.mceItemFlash, .mceItemShockWave, .mceItemQuickTime, .mceItemWindowsMedia, .mceItemRealMedia {border:1px dotted #cc0000; background-position:center; background-repeat:no-repeat; background-color:#ffffcc;} +.mceItemShockWave {background-image: url(../img/shockwave.gif);} +.mceItemFlash {background-image:url(../img/flash.gif);} +.mceItemQuickTime {background-image:url(../img/quicktime.gif);} +.mceItemWindowsMedia {background-image:url(../img/windowsmedia.gif);} +.mceItemRealMedia {background-image:url(../img/realmedia.gif);} diff --git a/web/js/globals/tinymce/plugins/media/css/media.css b/web/js/globals/tinymce/plugins/media/css/media.css new file mode 100644 index 0000000..2d08794 --- /dev/null +++ b/web/js/globals/tinymce/plugins/media/css/media.css @@ -0,0 +1,16 @@ +#id, #name, #hspace, #vspace, #class_name, #align { width: 100px } +#hspace, #vspace { width: 50px } +#flash_quality, #flash_align, #flash_scale, #flash_salign, #flash_wmode { width: 100px } +#flash_base, #flash_flashvars { width: 240px } +#width, #height { width: 40px } +#src, #media_type { width: 250px } +#class { width: 120px } +#prev { margin: 0; border: 1px solid black; width: 380px; height: 230px; overflow: auto } +.panel_wrapper div.current { height: 390px; overflow: auto } +#flash_options, #shockwave_options, #qt_options, #wmp_options, #rmp_options { display: none } +.mceAddSelectValue { background-color: #DDDDDD } +#qt_starttime, #qt_endtime, #qt_fov, #qt_href, #qt_moveid, #qt_moviename, #qt_node, #qt_pan, #qt_qtsrc, #qt_qtsrcchokespeed, #qt_target, #qt_tilt, #qt_urlsubstituten, #qt_volume { width: 70px } +#wmp_balance, #wmp_baseurl, #wmp_captioningid, #wmp_currentmarker, #wmp_currentposition, #wmp_defaultframe, #wmp_playcount, #wmp_rate, #wmp_uimode, #wmp_volume { width: 70px } +#rmp_console, #rmp_numloop, #rmp_controls, #rmp_scriptcallbacks { width: 70px } +#shockwave_swvolume, #shockwave_swframe, #shockwave_swurl, #shockwave_swstretchvalign, #shockwave_swstretchhalign, #shockwave_swstretchstyle { width: 90px } +#qt_qtsrc { width: 200px } diff --git a/web/js/globals/tinymce/plugins/media/editor_plugin.js b/web/js/globals/tinymce/plugins/media/editor_plugin.js new file mode 100644 index 0000000..4bbe367 --- /dev/null +++ b/web/js/globals/tinymce/plugins/media/editor_plugin.js @@ -0,0 +1 @@ +(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.MediaPlugin",{init:function(b,c){var e=this;e.editor=b;e.url=c;function f(g){return/^(mceItemFlash|mceItemShockWave|mceItemWindowsMedia|mceItemQuickTime|mceItemRealMedia)$/.test(g.className)}b.onPreInit.add(function(){b.serializer.addRules("param[name|value|_mce_value]")});b.addCommand("mceMedia",function(){b.windowManager.open({file:c+"/media.htm",width:430+parseInt(b.getLang("media.delta_width",0)),height:470+parseInt(b.getLang("media.delta_height",0)),inline:1},{plugin_url:c})});b.addButton("media",{title:"media.desc",cmd:"mceMedia"});b.onNodeChange.add(function(h,g,i){g.setActive("media",i.nodeName=="IMG"&&f(i))});b.onInit.add(function(){var g={mceItemFlash:"flash",mceItemShockWave:"shockwave",mceItemWindowsMedia:"windowsmedia",mceItemQuickTime:"quicktime",mceItemRealMedia:"realmedia"};b.selection.onSetContent.add(function(){e._spansToImgs(b.getBody())});b.selection.onBeforeSetContent.add(e._objectsToSpans,e);if(b.settings.content_css!==false){b.dom.loadCSS(c+"/css/content.css")}if(b.theme&&b.theme.onResolveName){b.theme.onResolveName.add(function(h,i){if(i.name=="img"){a(g,function(l,j){if(b.dom.hasClass(i.node,j)){i.name=l;i.title=b.dom.getAttrib(i.node,"title");return false}})}})}if(b&&b.plugins.contextmenu){b.plugins.contextmenu.onContextMenu.add(function(i,h,j){if(j.nodeName=="IMG"&&/mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(j.className)){h.add({title:"media.edit",icon:"media",cmd:"mceMedia"})}})}});b.onBeforeSetContent.add(e._objectsToSpans,e);b.onSetContent.add(function(){e._spansToImgs(b.getBody())});b.onPreProcess.add(function(g,i){var h=g.dom;if(i.set){e._spansToImgs(i.node);a(h.select("IMG",i.node),function(k){var j;if(f(k)){j=e._parse(k.title);h.setAttrib(k,"width",h.getAttrib(k,"width",j.width||100));h.setAttrib(k,"height",h.getAttrib(k,"height",j.height||100))}})}if(i.get){a(h.select("IMG",i.node),function(m){var l,j,k;if(g.getParam("media_use_script")){if(f(m)){m.className=m.className.replace(/mceItem/g,"mceTemp")}return}switch(m.className){case"mceItemFlash":l="d27cdb6e-ae6d-11cf-96b8-444553540000";j="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0";k="application/x-shockwave-flash";break;case"mceItemShockWave":l="166b1bca-3f9c-11cf-8075-444553540000";j="http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0";k="application/x-director";break;case"mceItemWindowsMedia":l=g.getParam("media_wmp6_compatible")?"05589fa1-c356-11ce-bf01-00aa0055595a":"6bf52a52-394a-11d3-b153-00c04f79faa6";j="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701";k="application/x-mplayer2";break;case"mceItemQuickTime":l="02bf25d5-8c17-4b23-bc80-d3488abddc6b";j="http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0";k="video/quicktime";break;case"mceItemRealMedia":l="cfcdaa03-8be4-11cf-b84b-0020afbbccfa";j="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0";k="audio/x-pn-realaudio-plugin";break}if(l){h.replace(e._buildObj({classid:l,codebase:j,type:k},m),m)}})}});b.onPostProcess.add(function(g,h){h.content=h.content.replace(/_mce_value=/g,"value=")});function d(g,h){h=new RegExp(h+'="([^"]+)"',"g").exec(g);return h?b.dom.decode(h[1]):""}b.onPostProcess.add(function(g,h){if(g.getParam("media_use_script")){h.content=h.content.replace(/]+>/g,function(j){var i=d(j,"class");if(/^(mceTempFlash|mceTempShockWave|mceTempWindowsMedia|mceTempQuickTime|mceTempRealMedia)$/.test(i)){at=e._parse(d(j,"title"));at.width=d(j,"width");at.height=d(j,"height");j=''; + } + + return im; + }); + } + }); + }, + + getInfo : function() { + return { + longname : 'Media', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + _objectsToSpans : function(ed, o) { + var t = this, h = o.content; + + h = h.replace(/]*>\s*write(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)\(\{([^\)]*)\}\);\s*<\/script>/gi, function(a, b, c) { + var o = t._parse(c); + + return '' + }); + + h = h.replace(/]*)>/gi, ''); + h = h.replace(/]*)\/?>/gi, ''); + h = h.replace(/]*)>/gi, ''); + h = h.replace(/<\/(object)([^>]*)>/gi, ''); + h = h.replace(/<\/embed>/gi, ''); + h = h.replace(/]*)>/gi, function(a, b) {return ''}); + h = h.replace(/\/ class=\"mceItemParam\"><\/span>/gi, 'class="mceItemParam">'); + + o.content = h; + }, + + _buildObj : function(o, n) { + var ob, ed = this.editor, dom = ed.dom, p = this._parse(n.title), stc; + + stc = ed.getParam('media_strict', true) && o.type == 'application/x-shockwave-flash'; + + p.width = o.width = dom.getAttrib(n, 'width') || 100; + p.height = o.height = dom.getAttrib(n, 'height') || 100; + + if (p.src) + p.src = ed.convertURL(p.src, 'src', n); + + if (stc) { + ob = dom.create('span', { + id : p.id, + _mce_name : 'object', + type : 'application/x-shockwave-flash', + data : p.src, + style : dom.getAttrib(n, 'style'), + width : o.width, + height : o.height + }); + } else { + ob = dom.create('span', { + id : p.id, + _mce_name : 'object', + classid : "clsid:" + o.classid, + style : dom.getAttrib(n, 'style'), + codebase : o.codebase, + width : o.width, + height : o.height + }); + } + + each (p, function(v, k) { + if (!/^(width|height|codebase|classid|id|_cx|_cy)$/.test(k)) { + // Use url instead of src in IE for Windows media + if (o.type == 'application/x-mplayer2' && k == 'src' && !p.url) + k = 'url'; + + if (v) + dom.add(ob, 'span', {_mce_name : 'param', name : k, '_mce_value' : v}); + } + }); + + if (!stc) + dom.add(ob, 'span', tinymce.extend({_mce_name : 'embed', type : o.type, style : dom.getAttrib(n, 'style')}, p)); + + return ob; + }, + + _spansToImgs : function(p) { + var t = this, dom = t.editor.dom, im, ci; + + each(dom.select('span', p), function(n) { + // Convert object into image + if (dom.getAttrib(n, 'class') == 'mceItemObject') { + ci = dom.getAttrib(n, "classid").toLowerCase().replace(/\s+/g, ''); + + switch (ci) { + case 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000': + dom.replace(t._createImg('mceItemFlash', n), n); + break; + + case 'clsid:166b1bca-3f9c-11cf-8075-444553540000': + dom.replace(t._createImg('mceItemShockWave', n), n); + break; + + case 'clsid:6bf52a52-394a-11d3-b153-00c04f79faa6': + case 'clsid:22d6f312-b0f6-11d0-94ab-0080c74c7e95': + case 'clsid:05589fa1-c356-11ce-bf01-00aa0055595a': + dom.replace(t._createImg('mceItemWindowsMedia', n), n); + break; + + case 'clsid:02bf25d5-8c17-4b23-bc80-d3488abddc6b': + dom.replace(t._createImg('mceItemQuickTime', n), n); + break; + + case 'clsid:cfcdaa03-8be4-11cf-b84b-0020afbbccfa': + dom.replace(t._createImg('mceItemRealMedia', n), n); + break; + + default: + dom.replace(t._createImg('mceItemFlash', n), n); + } + + return; + } + + // Convert embed into image + if (dom.getAttrib(n, 'class') == 'mceItemEmbed') { + switch (dom.getAttrib(n, 'type')) { + case 'application/x-shockwave-flash': + dom.replace(t._createImg('mceItemFlash', n), n); + break; + + case 'application/x-director': + dom.replace(t._createImg('mceItemShockWave', n), n); + break; + + case 'application/x-mplayer2': + dom.replace(t._createImg('mceItemWindowsMedia', n), n); + break; + + case 'video/quicktime': + dom.replace(t._createImg('mceItemQuickTime', n), n); + break; + + case 'audio/x-pn-realaudio-plugin': + dom.replace(t._createImg('mceItemRealMedia', n), n); + break; + + default: + dom.replace(t._createImg('mceItemFlash', n), n); + } + } + }); + }, + + _createImg : function(cl, n) { + var im, dom = this.editor.dom, pa = {}, ti = '', args; + + args = ['id', 'name', 'width', 'height', 'bgcolor', 'align', 'flashvars', 'src', 'wmode', 'allowfullscreen', 'quality', 'data']; + + // Create image + im = dom.create('img', { + src : this.url + '/img/trans.gif', + width : dom.getAttrib(n, 'width') || 100, + height : dom.getAttrib(n, 'height') || 100, + style : dom.getAttrib(n, 'style'), + 'class' : cl + }); + + // Setup base parameters + each(args, function(na) { + var v = dom.getAttrib(n, na); + + if (v) + pa[na] = v; + }); + + // Add optional parameters + each(dom.select('span', n), function(n) { + if (dom.hasClass(n, 'mceItemParam')) + pa[dom.getAttrib(n, 'name')] = dom.getAttrib(n, '_mce_value'); + }); + + // Use src not movie + if (pa.movie) { + pa.src = pa.movie; + delete pa.movie; + } + + // No src try data + if (!pa.src) { + pa.src = pa.data; + delete pa.data; + } + + // Merge with embed args + n = dom.select('.mceItemEmbed', n)[0]; + if (n) { + each(args, function(na) { + var v = dom.getAttrib(n, na); + + if (v && !pa[na]) + pa[na] = v; + }); + } + + delete pa.width; + delete pa.height; + + im.title = this._serialize(pa); + + return im; + }, + + _parse : function(s) { + return tinymce.util.JSON.parse('{' + s + '}'); + }, + + _serialize : function(o) { + return tinymce.util.JSON.serialize(o).replace(/[{}]/g, ''); + } + }); + + // Register plugin + tinymce.PluginManager.add('media', tinymce.plugins.MediaPlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/media/img/flash.gif b/web/js/globals/tinymce/plugins/media/img/flash.gif new file mode 100644 index 0000000000000000000000000000000000000000..cb192e6ceda8d19ad8e7d08dd1cfde0aa72ead2a GIT binary patch literal 241 zcmVOzlLa+Za}7>m0&NpCfJ0FQc3~F7DE)S%o1)Qi1n@vxX46qnD4hRS-NE*Pw!4UvE=#^N( literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/media/img/flv_player.swf b/web/js/globals/tinymce/plugins/media/img/flv_player.swf new file mode 100644 index 0000000000000000000000000000000000000000..042c2ab969e98a6fdbe08848c4a73bd2c41de906 GIT binary patch literal 11668 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 literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/media/img/quicktime.gif b/web/js/globals/tinymce/plugins/media/img/quicktime.gif new file mode 100644 index 0000000000000000000000000000000000000000..3b0499145b16138249f653a1a3f2c80230fb292c GIT binary patch literal 303 zcmV+~0nq+ONk%w1VGsZi0K^{vH>m7Qv+~s9^fsC5ZpZP=*zu3F=Jxpf8k_5u%JNv6 z=md-84VLU4w)kSE=yI&-yw>b=v+SqE?+kq47pC+YrR?bJ^yu>Zyvpn;hTp*6^mM!O zu+8$^=JX7bb<~J01ZTA{q@86#&8&6~H`Ss{{?p%K!-p%L6P2TpFYz90?pD06UU# BbnE~C literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/media/img/realmedia.gif b/web/js/globals/tinymce/plugins/media/img/realmedia.gif new file mode 100644 index 0000000000000000000000000000000000000000..fdfe0b9ac05869ae845fdd828eaad97cc0c69dbc GIT binary patch literal 439 zcmV;o0Z9HwNk%w1VI=?(0K^{vQcz8xz}f&njBB06v9GQ`Jv%NdDHCI&z`wqZw$(Lw zuFTBL!Pe#<92tv>h)9OE1Xh}vnVEHSaeb-GByg#tqM_B*)YRkdSdqTuipLaF8n=^^LJP4|1^gGRdo_Rl+a*grZQ1hw@Zo1ikN$oB{QbRq&z?QIckdq1aE3;Fq_(WV>Kc7gjQtQh+9OrtFhn-)LUqD<|MOIl_!(Ed#pPRE;S)g;ew3>pd zn`Wa(lc2DGa)peFw3f88dp-|`@*)AXj;@(8hwDr|7Sxsp;&YxjN*Y{PBB!TIU|!b7Zgv0OaG5)&Kwi literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/media/img/trans.gif b/web/js/globals/tinymce/plugins/media/img/trans.gif new file mode 100644 index 0000000000000000000000000000000000000000..388486517fa8da13ebd150e8f65d5096c3e10c3a GIT binary patch literal 43 ncmZ?wbhEHbWMp7un7{x9ia%KxMSyG_5FaGNz{KRj$Y2csb)f_x literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/media/img/windowsmedia.gif b/web/js/globals/tinymce/plugins/media/img/windowsmedia.gif new file mode 100644 index 0000000000000000000000000000000000000000..ab50f2d887a0843b116ef598e5a005e5601d18d0 GIT binary patch literal 415 zcmV;Q0bu?|Nk%w1VGjTg0M$PL`E^qkEu+z?1&N?x_*pRg{rx~kg!#|I<>uyug^O^t z0hZGrt*x!>$1C!zn`W5@`ts6_uMW)2%<0NUEKIo?SIPPE=}U0}7Z(?JcX!y=*;bF< zCWz-=h7+2ao9)(dOHM;+X=xs9)%!~xc&ICMZdRYdUQ2$^@9y(6X3NCIz{cM7f^Z=Q z1_tQ95kgl8b%R%OiYTIo7LSdE^@}A^8LW002J#EC2ui01p5U000KOz@O0K01zUifeIyT9%!RzMDgehG|mwLz+Eh; z7Z~iE zrX?OfJ^>XeDJK)xJuWOB3_l1N0Ra>g4Gk^=ED0V6LI?>4;Q|6OB{LplLMRLg8U5-E J?0y6R06W6!pgRBn literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/media/js/embed.js b/web/js/globals/tinymce/plugins/media/js/embed.js new file mode 100644 index 0000000..f8dc810 --- /dev/null +++ b/web/js/globals/tinymce/plugins/media/js/embed.js @@ -0,0 +1,73 @@ +/** + * This script contains embed functions for common plugins. This scripts are complety free to use for any purpose. + */ + +function writeFlash(p) { + writeEmbed( + 'D27CDB6E-AE6D-11cf-96B8-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'application/x-shockwave-flash', + p + ); +} + +function writeShockWave(p) { + writeEmbed( + '166B1BCA-3F9C-11CF-8075-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0', + 'application/x-director', + p + ); +} + +function writeQuickTime(p) { + writeEmbed( + '02BF25D5-8C17-4B23-BC80-D3488ABDDC6B', + 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0', + 'video/quicktime', + p + ); +} + +function writeRealMedia(p) { + writeEmbed( + 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'audio/x-pn-realaudio-plugin', + p + ); +} + +function writeWindowsMedia(p) { + p.url = p.src; + writeEmbed( + '6BF52A52-394A-11D3-B153-00C04F79FAA6', + 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701', + 'application/x-mplayer2', + p + ); +} + +function writeEmbed(cls, cb, mt, p) { + var h = '', n; + + h += ''; + + h += ''); + +function init() { + var pl = "", f, val; + var type = "flash", fe, i; + + ed = tinyMCEPopup.editor; + + tinyMCEPopup.resizeToInnerSize(); + f = document.forms[0] + + fe = ed.selection.getNode(); + if (/mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(ed.dom.getAttrib(fe, 'class'))) { + pl = fe.title; + + switch (ed.dom.getAttrib(fe, 'class')) { + case 'mceItemFlash': + type = 'flash'; + break; + + case 'mceItemFlashVideo': + type = 'flv'; + break; + + case 'mceItemShockWave': + type = 'shockwave'; + break; + + case 'mceItemWindowsMedia': + type = 'wmp'; + break; + + case 'mceItemQuickTime': + type = 'qt'; + break; + + case 'mceItemRealMedia': + type = 'rmp'; + break; + } + + document.forms[0].insert.value = ed.getLang('update', 'Insert', true); + } + + document.getElementById('filebrowsercontainer').innerHTML = getBrowserHTML('filebrowser','src','media','media'); + document.getElementById('qtsrcfilebrowsercontainer').innerHTML = getBrowserHTML('qtsrcfilebrowser','qt_qtsrc','media','media'); + document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); + + var html = getMediaListHTML('medialist','src','media','media'); + if (html == "") + document.getElementById("linklistrow").style.display = 'none'; + else + document.getElementById("linklistcontainer").innerHTML = html; + + // Resize some elements + if (isVisible('filebrowser')) + document.getElementById('src').style.width = '230px'; + + // Setup form + if (pl != "") { + pl = tinyMCEPopup.editor.plugins.media._parse(pl); + + switch (type) { + case "flash": + setBool(pl, 'flash', 'play'); + setBool(pl, 'flash', 'loop'); + setBool(pl, 'flash', 'menu'); + setBool(pl, 'flash', 'swliveconnect'); + setStr(pl, 'flash', 'quality'); + setStr(pl, 'flash', 'scale'); + setStr(pl, 'flash', 'salign'); + setStr(pl, 'flash', 'wmode'); + setStr(pl, 'flash', 'base'); + setStr(pl, 'flash', 'flashvars'); + break; + + case "qt": + setBool(pl, 'qt', 'loop'); + setBool(pl, 'qt', 'autoplay'); + setBool(pl, 'qt', 'cache'); + setBool(pl, 'qt', 'controller'); + setBool(pl, 'qt', 'correction'); + setBool(pl, 'qt', 'enablejavascript'); + setBool(pl, 'qt', 'kioskmode'); + setBool(pl, 'qt', 'autohref'); + setBool(pl, 'qt', 'playeveryframe'); + setBool(pl, 'qt', 'tarsetcache'); + setStr(pl, 'qt', 'scale'); + setStr(pl, 'qt', 'starttime'); + setStr(pl, 'qt', 'endtime'); + setStr(pl, 'qt', 'tarset'); + setStr(pl, 'qt', 'qtsrcchokespeed'); + setStr(pl, 'qt', 'volume'); + setStr(pl, 'qt', 'qtsrc'); + break; + + case "shockwave": + setBool(pl, 'shockwave', 'sound'); + setBool(pl, 'shockwave', 'progress'); + setBool(pl, 'shockwave', 'autostart'); + setBool(pl, 'shockwave', 'swliveconnect'); + setStr(pl, 'shockwave', 'swvolume'); + setStr(pl, 'shockwave', 'swstretchstyle'); + setStr(pl, 'shockwave', 'swstretchhalign'); + setStr(pl, 'shockwave', 'swstretchvalign'); + break; + + case "wmp": + setBool(pl, 'wmp', 'autostart'); + setBool(pl, 'wmp', 'enabled'); + setBool(pl, 'wmp', 'enablecontextmenu'); + setBool(pl, 'wmp', 'fullscreen'); + setBool(pl, 'wmp', 'invokeurls'); + setBool(pl, 'wmp', 'mute'); + setBool(pl, 'wmp', 'stretchtofit'); + setBool(pl, 'wmp', 'windowlessvideo'); + setStr(pl, 'wmp', 'balance'); + setStr(pl, 'wmp', 'baseurl'); + setStr(pl, 'wmp', 'captioningid'); + setStr(pl, 'wmp', 'currentmarker'); + setStr(pl, 'wmp', 'currentposition'); + setStr(pl, 'wmp', 'defaultframe'); + setStr(pl, 'wmp', 'playcount'); + setStr(pl, 'wmp', 'rate'); + setStr(pl, 'wmp', 'uimode'); + setStr(pl, 'wmp', 'volume'); + break; + + case "rmp": + setBool(pl, 'rmp', 'autostart'); + setBool(pl, 'rmp', 'loop'); + setBool(pl, 'rmp', 'autogotourl'); + setBool(pl, 'rmp', 'center'); + setBool(pl, 'rmp', 'imagestatus'); + setBool(pl, 'rmp', 'maintainaspect'); + setBool(pl, 'rmp', 'nojava'); + setBool(pl, 'rmp', 'prefetch'); + setBool(pl, 'rmp', 'shuffle'); + setStr(pl, 'rmp', 'console'); + setStr(pl, 'rmp', 'controls'); + setStr(pl, 'rmp', 'numloop'); + setStr(pl, 'rmp', 'scriptcallbacks'); + break; + } + + setStr(pl, null, 'src'); + setStr(pl, null, 'id'); + setStr(pl, null, 'name'); + setStr(pl, null, 'vspace'); + setStr(pl, null, 'hspace'); + setStr(pl, null, 'bgcolor'); + setStr(pl, null, 'align'); + setStr(pl, null, 'width'); + setStr(pl, null, 'height'); + + if ((val = ed.dom.getAttrib(fe, "width")) != "") + pl.width = f.width.value = val; + + if ((val = ed.dom.getAttrib(fe, "height")) != "") + pl.height = f.height.value = val; + + oldWidth = pl.width ? parseInt(pl.width) : 0; + oldHeight = pl.height ? parseInt(pl.height) : 0; + } else + oldWidth = oldHeight = 0; + + selectByValue(f, 'media_type', type); + changedType(type); + updateColor('bgcolor_pick', 'bgcolor'); + + TinyMCE_EditableSelects.init(); + generatePreview(); +} + +function insertMedia() { + var fe, f = document.forms[0], h; + + tinyMCEPopup.restoreSelection(); + + if (!AutoValidator.validate(f)) { + tinyMCEPopup.alert(ed.getLang('invalid_data')); + return false; + } + + f.width.value = f.width.value == "" ? 100 : f.width.value; + f.height.value = f.height.value == "" ? 100 : f.height.value; + + fe = ed.selection.getNode(); + if (fe != null && /mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(ed.dom.getAttrib(fe, 'class'))) { + switch (f.media_type.options[f.media_type.selectedIndex].value) { + case "flash": + fe.className = "mceItemFlash"; + break; + + case "flv": + fe.className = "mceItemFlashVideo"; + break; + + case "shockwave": + fe.className = "mceItemShockWave"; + break; + + case "qt": + fe.className = "mceItemQuickTime"; + break; + + case "wmp": + fe.className = "mceItemWindowsMedia"; + break; + + case "rmp": + fe.className = "mceItemRealMedia"; + break; + } + + if (fe.width != f.width.value || fe.height != f.height.value) + ed.execCommand('mceRepaint'); + + fe.title = serializeParameters(); + fe.width = f.width.value; + fe.height = f.height.value; + fe.style.width = f.width.value + (f.width.value.indexOf('%') == -1 ? 'px' : ''); + fe.style.height = f.height.value + (f.height.value.indexOf('%') == -1 ? 'px' : ''); + fe.align = f.align.options[f.align.selectedIndex].value; + } else { + h = ' 0) { + var html = ""; + + html += ''; + + return html; + } + + return ""; +} + +function getType(v) { + var fo, i, c, el, x, f = document.forms[0]; + + fo = ed.getParam("media_types", "flash=swf;flv=flv;shockwave=dcr;qt=mov,qt,mpg,mp3,mp4,mpeg;shockwave=dcr;wmp=avi,wmv,wm,asf,asx,wmx,wvx;rmp=rm,ra,ram").split(';'); + + // YouTube + if (v.match(/watch\?v=(.+)(.*)/)) { + f.width.value = '425'; + f.height.value = '350'; + f.src.value = 'http://www.youtube.com/v/' + v.match(/v=(.*)(.*)/)[0].split('=')[1]; + return 'flash'; + } + + // Google video + if (v.indexOf('http://video.google.com/videoplay?docid=') == 0) { + f.width.value = '425'; + f.height.value = '326'; + f.src.value = 'http://video.google.com/googleplayer.swf?docId=' + v.substring('http://video.google.com/videoplay?docid='.length) + '&hl=en'; + return 'flash'; + } + + for (i=0; i 0 ? s.substring(0, s.length - 1) : s; + + return s; +} + +function setBool(pl, p, n) { + if (typeof(pl[n]) == "undefined") + return; + + document.forms[0].elements[p + "_" + n].checked = pl[n] != 'false'; +} + +function setStr(pl, p, n) { + var f = document.forms[0], e = f.elements[(p != null ? p + "_" : '') + n]; + + if (typeof(pl[n]) == "undefined") + return; + + if (e.type == "text") + e.value = pl[n]; + else + selectByValue(f, (p != null ? p + "_" : '') + n, pl[n]); +} + +function getBool(p, n, d, tv, fv) { + var v = document.forms[0].elements[p + "_" + n].checked; + + tv = typeof(tv) == 'undefined' ? 'true' : "'" + jsEncode(tv) + "'"; + fv = typeof(fv) == 'undefined' ? 'false' : "'" + jsEncode(fv) + "'"; + + return (v == d) ? '' : n + (v ? ':' + tv + ',' : ":\'" + fv + "\',"); +} + +function getStr(p, n, d) { + var e = document.forms[0].elements[(p != null ? p + "_" : "") + n]; + var v = e.type == "text" ? e.value : e.options[e.selectedIndex].value; + + if (n == 'src') + v = tinyMCEPopup.editor.convertURL(v, 'src', null); + + return ((n == d || v == '') ? '' : n + ":'" + jsEncode(v) + "',"); +} + +function getInt(p, n, d) { + var e = document.forms[0].elements[(p != null ? p + "_" : "") + n]; + var v = e.type == "text" ? e.value : e.options[e.selectedIndex].value; + + return ((n == d || v == '') ? '' : n + ":" + v.replace(/[^0-9]+/g, '') + ","); +} + +function jsEncode(s) { + s = s.replace(new RegExp('\\\\', 'g'), '\\\\'); + s = s.replace(new RegExp('"', 'g'), '\\"'); + s = s.replace(new RegExp("'", 'g'), "\\'"); + + return s; +} + +function generatePreview(c) { + var f = document.forms[0], p = document.getElementById('prev'), h = '', cls, pl, n, type, codebase, wp, hp, nw, nh; + + p.innerHTML = ''; + + nw = parseInt(f.width.value); + nh = parseInt(f.height.value); + + if (f.width.value != "" && f.height.value != "") { + if (f.constrain.checked) { + if (c == 'width' && oldWidth != 0) { + wp = nw / oldWidth; + nh = Math.round(wp * nh); + f.height.value = nh; + } else if (c == 'height' && oldHeight != 0) { + hp = nh / oldHeight; + nw = Math.round(hp * nw); + f.width.value = nw; + } + } + } + + if (f.width.value != "") + oldWidth = nw; + + if (f.height.value != "") + oldHeight = nh; + + // After constrain + pl = serializeParameters(); + + switch (f.media_type.options[f.media_type.selectedIndex].value) { + case "flash": + cls = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; + codebase = 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'; + type = 'application/x-shockwave-flash'; + break; + + case "shockwave": + cls = 'clsid:166B1BCA-3F9C-11CF-8075-444553540000'; + codebase = 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0'; + type = 'application/x-director'; + break; + + case "qt": + cls = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B'; + codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0'; + type = 'video/quicktime'; + break; + + case "wmp": + cls = ed.getParam('media_wmp6_compatible') ? 'clsid:05589FA1-C356-11CE-BF01-00AA0055595A' : 'clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6'; + codebase = 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701'; + type = 'application/x-mplayer2'; + break; + + case "rmp": + cls = 'clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA'; + codebase = 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701'; + type = 'audio/x-pn-realaudio-plugin'; + break; + } + + if (pl == '') { + p.innerHTML = ''; + return; + } + + pl = tinyMCEPopup.editor.plugins.media._parse(pl); + + if (!pl.src) { + p.innerHTML = ''; + return; + } + + pl.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(pl.src); + pl.width = !pl.width ? 100 : pl.width; + pl.height = !pl.height ? 100 : pl.height; + pl.id = !pl.id ? 'obj' : pl.id; + pl.name = !pl.name ? 'eobj' : pl.name; + pl.align = !pl.align ? '' : pl.align; + + // Avoid annoying warning about insecure items + if (!tinymce.isIE || document.location.protocol != 'https:') { + h += ''; + + for (n in pl) { + h += ''; + + // Add extra url parameter if it's an absolute URL + if (n == 'src' && pl[n].indexOf('://') != -1) + h += ''; + } + } + + h += ' + + + {#media_dlg.title} + + + + + + + + + +
      + + +
      +
      +
      + {#media_dlg.general} + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + +
       
      +
      + + + + + + +
      x   
      +
      +
      + +
      + {#media_dlg.preview} + +
      +
      + +
      +
      + {#media_dlg.advanced} + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + + + +
       
      +
      +
      + +
      + {#media_dlg.flash_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + +
      + + + +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + + + + + + + +
      +
      + +
      + {#media_dlg.flv_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      +
      + +
      + {#media_dlg.qt_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      +  
      + + + + + +
       
      +
      +
      + +
      + {#media_dlg.wmp_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      +
      + +
      + {#media_dlg.rmp_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      +   +
      +
      + +
      + {#media_dlg.shockwave_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      + + + + + +
      +
      +
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/nonbreaking/editor_plugin.js b/web/js/globals/tinymce/plugins/nonbreaking/editor_plugin.js new file mode 100644 index 0000000..eb40a6a --- /dev/null +++ b/web/js/globals/tinymce/plugins/nonbreaking/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.Nonbreaking",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceNonBreaking",function(){a.execCommand("mceInsertContent",false,(a.plugins.visualchars&&a.plugins.visualchars.state)?' ':" ")});a.addButton("nonbreaking",{title:"nonbreaking.nonbreaking_desc",cmd:"mceNonBreaking"});if(a.getParam("nonbreaking_force_tab")){a.onKeyDown.add(function(d,f){if(tinymce.isIE&&f.keyCode==9){d.execCommand("mceNonBreaking");d.execCommand("mceNonBreaking");d.execCommand("mceNonBreaking");tinymce.dom.Event.cancel(f)}})}},getInfo:function(){return{longname:"Nonbreaking space",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/nonbreaking",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("nonbreaking",tinymce.plugins.Nonbreaking)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/nonbreaking/editor_plugin_src.js b/web/js/globals/tinymce/plugins/nonbreaking/editor_plugin_src.js new file mode 100644 index 0000000..ca83ee2 --- /dev/null +++ b/web/js/globals/tinymce/plugins/nonbreaking/editor_plugin_src.js @@ -0,0 +1,53 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Nonbreaking', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceNonBreaking', function() { + ed.execCommand('mceInsertContent', false, (ed.plugins.visualchars && ed.plugins.visualchars.state) ? ' ' : ' '); + }); + + // Register buttons + ed.addButton('nonbreaking', {title : 'nonbreaking.nonbreaking_desc', cmd : 'mceNonBreaking'}); + + if (ed.getParam('nonbreaking_force_tab')) { + ed.onKeyDown.add(function(ed, e) { + if (tinymce.isIE && e.keyCode == 9) { + ed.execCommand('mceNonBreaking'); + ed.execCommand('mceNonBreaking'); + ed.execCommand('mceNonBreaking'); + tinymce.dom.Event.cancel(e); + } + }); + } + }, + + getInfo : function() { + return { + longname : 'Nonbreaking space', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/nonbreaking', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + + // Private methods + }); + + // Register plugin + tinymce.PluginManager.add('nonbreaking', tinymce.plugins.Nonbreaking); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/noneditable/editor_plugin.js b/web/js/globals/tinymce/plugins/noneditable/editor_plugin.js new file mode 100644 index 0000000..9945cd8 --- /dev/null +++ b/web/js/globals/tinymce/plugins/noneditable/editor_plugin.js @@ -0,0 +1 @@ +(function(){var a=tinymce.dom.Event;tinymce.create("tinymce.plugins.NonEditablePlugin",{init:function(d,e){var f=this,c,b;f.editor=d;c=d.getParam("noneditable_editable_class","mceEditable");b=d.getParam("noneditable_noneditable_class","mceNonEditable");d.onNodeChange.addToTop(function(h,g,k){var j,i;j=h.dom.getParent(h.selection.getStart(),function(l){return h.dom.hasClass(l,b)});i=h.dom.getParent(h.selection.getEnd(),function(l){return h.dom.hasClass(l,b)});if(j||i){f._setDisabled(1);return false}else{f._setDisabled(0)}})},getInfo:function(){return{longname:"Non editable elements",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_block:function(c,d){var b=d.keyCode;if((b>32&&b<41)||(b>111&&b<124)){return}return a.cancel(d)},_setDisabled:function(d){var c=this,b=c.editor;tinymce.each(b.controlManager.controls,function(e){e.setDisabled(d)});if(d!==c.disabled){if(d){b.onKeyDown.addToTop(c._block);b.onKeyPress.addToTop(c._block);b.onKeyUp.addToTop(c._block);b.onPaste.addToTop(c._block)}else{b.onKeyDown.remove(c._block);b.onKeyPress.remove(c._block);b.onKeyUp.remove(c._block);b.onPaste.remove(c._block)}c.disabled=d}}});tinymce.PluginManager.add("noneditable",tinymce.plugins.NonEditablePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/noneditable/editor_plugin_src.js b/web/js/globals/tinymce/plugins/noneditable/editor_plugin_src.js new file mode 100644 index 0000000..656c971 --- /dev/null +++ b/web/js/globals/tinymce/plugins/noneditable/editor_plugin_src.js @@ -0,0 +1,90 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var Event = tinymce.dom.Event; + + tinymce.create('tinymce.plugins.NonEditablePlugin', { + init : function(ed, url) { + var t = this, editClass, nonEditClass; + + t.editor = ed; + editClass = ed.getParam("noneditable_editable_class", "mceEditable"); + nonEditClass = ed.getParam("noneditable_noneditable_class", "mceNonEditable"); + + ed.onNodeChange.addToTop(function(ed, cm, n) { + var sc, ec; + + // Block if start or end is inside a non editable element + sc = ed.dom.getParent(ed.selection.getStart(), function(n) { + return ed.dom.hasClass(n, nonEditClass); + }); + + ec = ed.dom.getParent(ed.selection.getEnd(), function(n) { + return ed.dom.hasClass(n, nonEditClass); + }); + + // Block or unblock + if (sc || ec) { + t._setDisabled(1); + return false; + } else + t._setDisabled(0); + }); + }, + + getInfo : function() { + return { + longname : 'Non editable elements', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _block : function(ed, e) { + var k = e.keyCode; + + // Don't block arrow keys, pg up/down, and F1-F12 + if ((k > 32 && k < 41) || (k > 111 && k < 124)) + return; + + return Event.cancel(e); + }, + + _setDisabled : function(s) { + var t = this, ed = t.editor; + + tinymce.each(ed.controlManager.controls, function(c) { + c.setDisabled(s); + }); + + if (s !== t.disabled) { + if (s) { + ed.onKeyDown.addToTop(t._block); + ed.onKeyPress.addToTop(t._block); + ed.onKeyUp.addToTop(t._block); + ed.onPaste.addToTop(t._block); + } else { + ed.onKeyDown.remove(t._block); + ed.onKeyPress.remove(t._block); + ed.onKeyUp.remove(t._block); + ed.onPaste.remove(t._block); + } + + t.disabled = s; + } + } + }); + + // Register plugin + tinymce.PluginManager.add('noneditable', tinymce.plugins.NonEditablePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/pagebreak/css/content.css b/web/js/globals/tinymce/plugins/pagebreak/css/content.css new file mode 100644 index 0000000..c949d58 --- /dev/null +++ b/web/js/globals/tinymce/plugins/pagebreak/css/content.css @@ -0,0 +1 @@ +.mcePageBreak {display:block;border:0;width:100%;height:12px;border-top:1px dotted #ccc;margin-top:15px;background:#fff url(../img/pagebreak.gif) no-repeat center top;} diff --git a/web/js/globals/tinymce/plugins/pagebreak/editor_plugin.js b/web/js/globals/tinymce/plugins/pagebreak/editor_plugin.js new file mode 100644 index 0000000..a212f69 --- /dev/null +++ b/web/js/globals/tinymce/plugins/pagebreak/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.PageBreakPlugin",{init:function(b,d){var f='',a="mcePageBreak",c=b.getParam("pagebreak_separator",""),e;e=new RegExp(c.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(g){return"\\"+g}),"g");b.addCommand("mcePageBreak",function(){b.execCommand("mceInsertContent",0,f)});b.addButton("pagebreak",{title:"pagebreak.desc",cmd:a});b.onInit.add(function(){if(b.settings.content_css!==false){b.dom.loadCSS(d+"/css/content.css")}if(b.theme.onResolveName){b.theme.onResolveName.add(function(g,h){if(h.node.nodeName=="IMG"&&b.dom.hasClass(h.node,a)){h.name="pagebreak"}})}});b.onClick.add(function(g,h){h=h.target;if(h.nodeName==="IMG"&&g.dom.hasClass(h,a)){g.selection.select(h)}});b.onNodeChange.add(function(h,g,i){g.setActive("pagebreak",i.nodeName==="IMG"&&h.dom.hasClass(i,a))});b.onBeforeSetContent.add(function(g,h){h.content=h.content.replace(e,f)});b.onPostProcess.add(function(g,h){if(h.get){h.content=h.content.replace(/]+>/g,function(i){if(i.indexOf('class="mcePageBreak')!==-1){i=c}return i})}})},getInfo:function(){return{longname:"PageBreak",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("pagebreak",tinymce.plugins.PageBreakPlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/pagebreak/editor_plugin_src.js b/web/js/globals/tinymce/plugins/pagebreak/editor_plugin_src.js new file mode 100644 index 0000000..4e1eb0a --- /dev/null +++ b/web/js/globals/tinymce/plugins/pagebreak/editor_plugin_src.js @@ -0,0 +1,77 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.PageBreakPlugin', { + init : function(ed, url) { + var pb = '', cls = 'mcePageBreak', sep = ed.getParam('pagebreak_separator', ''), pbRE; + + pbRE = new RegExp(sep.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g, function(a) {return '\\' + a;}), 'g'); + + // Register commands + ed.addCommand('mcePageBreak', function() { + ed.execCommand('mceInsertContent', 0, pb); + }); + + // Register buttons + ed.addButton('pagebreak', {title : 'pagebreak.desc', cmd : cls}); + + ed.onInit.add(function() { + if (ed.settings.content_css !== false) + ed.dom.loadCSS(url + "/css/content.css"); + + if (ed.theme.onResolveName) { + ed.theme.onResolveName.add(function(th, o) { + if (o.node.nodeName == 'IMG' && ed.dom.hasClass(o.node, cls)) + o.name = 'pagebreak'; + }); + } + }); + + ed.onClick.add(function(ed, e) { + e = e.target; + + if (e.nodeName === 'IMG' && ed.dom.hasClass(e, cls)) + ed.selection.select(e); + }); + + ed.onNodeChange.add(function(ed, cm, n) { + cm.setActive('pagebreak', n.nodeName === 'IMG' && ed.dom.hasClass(n, cls)); + }); + + ed.onBeforeSetContent.add(function(ed, o) { + o.content = o.content.replace(pbRE, pb); + }); + + ed.onPostProcess.add(function(ed, o) { + if (o.get) + o.content = o.content.replace(/]+>/g, function(im) { + if (im.indexOf('class="mcePageBreak') !== -1) + im = sep; + + return im; + }); + }); + }, + + getInfo : function() { + return { + longname : 'PageBreak', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('pagebreak', tinymce.plugins.PageBreakPlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/pagebreak/img/pagebreak.gif b/web/js/globals/tinymce/plugins/pagebreak/img/pagebreak.gif new file mode 100644 index 0000000000000000000000000000000000000000..acdf4085f3068c4c0a1d6855f4b80dae8bac3068 GIT binary patch literal 325 zcmV-L0lNN2Nk%w1VPpUd0J9GO`>v<{=;ru;boX6P{`2zsmyZ3>&HK5t_;hIbi-G;z z+4`cI{KdfcXj}GCLjV8&A^8LW000jFEC2ui0Av6R000E?@X1N5y*TU5yZ>M)j$|1M z4Ouvb$pHu>IW8BZq|n;U0s@T!VM5~w1_+1X!EiVl!&PITYdjT!ffYfpt{jAfv%qvh zA63WUHSlr7LkeyaV4(pM0f50(II?RD4RtMg4-E+tFhdAy5{3c=0}3Bg9Y8`B2To20 zR%SO62L%9}0H+dzoKB$+2TOwzUrwi{XiBM^4V#>63q3!LsU3u93zH8CdwqY%62;1g z0g8ze$k93lWExp`CUe|K4qOWk17ZeJ0|5pDP6+}};{>bI@lOWj=kf}r2sHp7w9-Ie XK%9UG6W(*AX-vY05F<*&5CH%?Gwy&_ literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/pagebreak/img/trans.gif b/web/js/globals/tinymce/plugins/pagebreak/img/trans.gif new file mode 100644 index 0000000000000000000000000000000000000000..388486517fa8da13ebd150e8f65d5096c3e10c3a GIT binary patch literal 43 ncmZ?wbhEHbWMp7un7{x9ia%KxMSyG_5FaGNz{KRj$Y2csb)f_x literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/paste/editor_plugin.js b/web/js/globals/tinymce/plugins/paste/editor_plugin.js new file mode 100644 index 0000000..3785ab2 --- /dev/null +++ b/web/js/globals/tinymce/plugins/paste/editor_plugin.js @@ -0,0 +1 @@ +(function(){var c=tinymce.each,d=null,a={paste_auto_cleanup_on_paste:true,paste_block_drop:false,paste_retain_style_properties:"none",paste_strip_class_attributes:"mso",paste_remove_spans:false,paste_remove_styles:false,paste_remove_styles_if_webkit:true,paste_convert_middot_lists:true,paste_convert_headers_to_strong:false,paste_dialog_width:"450",paste_dialog_height:"400",paste_text_use_dialog:false,paste_text_sticky:false,paste_text_notifyalways:false,paste_text_linebreaktype:"p",paste_text_replacements:[[/\u2026/g,"..."],[/[\x93\x94\u201c\u201d]/g,'"'],[/[\x60\x91\x92\u2018\u2019]/g,"'"]]};function b(e,f){return e.getParam(f,a[f])}tinymce.create("tinymce.plugins.PastePlugin",{init:function(e,f){var g=this;g.editor=e;g.url=f;g.onPreProcess=new tinymce.util.Dispatcher(g);g.onPostProcess=new tinymce.util.Dispatcher(g);g.onPreProcess.add(g._preProcess);g.onPostProcess.add(g._postProcess);g.onPreProcess.add(function(j,k){e.execCallback("paste_preprocess",j,k)});g.onPostProcess.add(function(j,k){e.execCallback("paste_postprocess",j,k)});e.pasteAsPlainText=false;function i(l,j){var k=e.dom;g.onPreProcess.dispatch(g,l);l.node=k.create("div",0,l.content);g.onPostProcess.dispatch(g,l);l.content=e.serializer.serialize(l.node,{getInner:1});if((!j)&&(e.pasteAsPlainText)){g._insertPlainText(e,k,l.content);if(!b(e,"paste_text_sticky")){e.pasteAsPlainText=false;e.controlManager.setActive("pastetext",false)}}else{if(/<(p|h[1-6]|ul|ol)/.test(l.content)){g._insertBlockContent(e,k,l.content)}else{g._insert(l.content)}}}e.addCommand("mceInsertClipboardContent",function(j,k){i(k,true)});if(!b(e,"paste_text_use_dialog")){e.addCommand("mcePasteText",function(k,j){var l=tinymce.util.Cookie;e.pasteAsPlainText=!e.pasteAsPlainText;e.controlManager.setActive("pastetext",e.pasteAsPlainText);if((e.pasteAsPlainText)&&(!l.get("tinymcePasteText"))){if(b(e,"paste_text_sticky")){e.windowManager.alert(e.translate("paste.plaintext_mode_sticky"))}else{e.windowManager.alert(e.translate("paste.plaintext_mode_sticky"))}if(!b(e,"paste_text_notifyalways")){l.set("tinymcePasteText","1",new Date(new Date().getFullYear()+1,12,31))}}})}e.addButton("pastetext",{title:"paste.paste_text_desc",cmd:"mcePasteText"});e.addButton("selectall",{title:"paste.selectall_desc",cmd:"selectall"});function h(s){var m,q,k,l=e.selection,p=e.dom,r=e.getBody(),j;if(e.pasteAsPlainText&&(s.clipboardData||p.doc.dataTransfer)){s.preventDefault();i({content:(s.clipboardData||p.doc.dataTransfer).getData("Text")},true);return}if(p.get("_mcePaste")){return}m=p.add(r,"div",{id:"_mcePaste","class":"mcePaste"},'\uFEFF
      ');if(r!=e.getDoc().body){j=p.getPos(e.selection.getStart(),r).y}else{j=r.scrollTop}p.setStyles(m,{position:"absolute",left:-10000,top:j,width:1,height:1,overflow:"hidden"});if(tinymce.isIE){k=p.doc.body.createTextRange();k.moveToElementText(m);k.execCommand("Paste");p.remove(m);if(m.innerHTML==="\uFEFF"){e.execCommand("mcePasteWord");s.preventDefault();return}i({content:m.innerHTML});return tinymce.dom.Event.cancel(s)}else{function o(n){n.preventDefault()}p.bind(e.getDoc(),"mousedown",o);p.bind(e.getDoc(),"keydown",o);q=e.selection.getRng();m=m.firstChild;k=e.getDoc().createRange();k.setStart(m,0);k.setEnd(m,1);l.setRng(k);window.setTimeout(function(){var t="",n=p.select("div.mcePaste");c(n,function(v){var u=v.firstChild;if(u&&u.nodeName=="DIV"&&u.style.marginTop&&u.style.backgroundColor){p.remove(u,1)}c(p.select("div.mcePaste",v),function(w){p.remove(w,1)});c(p.select("span.Apple-style-span",v),function(w){p.remove(w,1)});c(p.select("br[_mce_bogus]",v),function(w){p.remove(w)});t+=v.innerHTML});c(n,function(u){p.remove(u)});if(q){l.setRng(q)}i({content:t});p.unbind(e.getDoc(),"mousedown",o);p.unbind(e.getDoc(),"keydown",o)},0)}}if(b(e,"paste_auto_cleanup_on_paste")){if(tinymce.isOpera||/Firefox\/2/.test(navigator.userAgent)){e.onKeyDown.add(function(j,k){if(((tinymce.isMac?k.metaKey:k.ctrlKey)&&k.keyCode==86)||(k.shiftKey&&k.keyCode==45)){h(k)}})}else{e.onPaste.addToTop(function(j,k){return h(k)})}}if(b(e,"paste_block_drop")){e.onInit.add(function(){e.dom.bind(e.getBody(),["dragend","dragover","draggesture","dragdrop","drop","drag"],function(j){j.preventDefault();j.stopPropagation();return false})})}g._legacySupport()},getInfo:function(){return{longname:"Paste text/word",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_preProcess:function(i,f){var l=this.editor,k=f.content,q=tinymce.grep,p=tinymce.explode,g=tinymce.trim,m,j;function e(h){c(h,function(o){if(o.constructor==RegExp){k=k.replace(o,"")}else{k=k.replace(o[0],o[1])}})}if(/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(k)||f.wordContent){f.wordContent=true;e([/^\s*( )+/gi,/( |]*>)+\s*$/gi]);if(b(l,"paste_convert_headers_to_strong")){k=k.replace(/

      ]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi,"

      $1

      ")}if(b(l,"paste_convert_middot_lists")){e([[//gi,"$&__MCE_ITEM__"],[/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi,"$1__MCE_ITEM__"]])}e([//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\u00a0"]]);do{m=k.length;k=k.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi,"$1")}while(m!=k.length);if(b(l,"paste_retain_style_properties").replace(/^none$/i,"").length==0){k=k.replace(/<\/?span[^>]*>/gi,"")}else{e([[/([\s\u00a0]*)<\/span>/gi,function(o,h){return(h.length>0)?h.replace(/./," ").slice(Math.floor(h.length/2)).split("").join("\u00a0"):""}],[/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,function(u,h,t){var v=[],o=0,r=p(g(t).replace(/"/gi,"'"),";");c(r,function(s){var w,y,z=p(s,":");function x(A){return A+((A!=="0")&&(/\d$/.test(A)))?"px":""}if(z.length==2){w=z[0].toLowerCase();y=z[1].toLowerCase();switch(w){case"mso-padding-alt":case"mso-padding-top-alt":case"mso-padding-right-alt":case"mso-padding-bottom-alt":case"mso-padding-left-alt":case"mso-margin-alt":case"mso-margin-top-alt":case"mso-margin-right-alt":case"mso-margin-bottom-alt":case"mso-margin-left-alt":case"mso-table-layout-alt":case"mso-height":case"mso-width":case"mso-vertical-align-alt":v[o++]=w.replace(/^mso-|-alt$/g,"")+":"+x(y);return;case"horiz-align":v[o++]="text-align:"+y;return;case"vert-align":v[o++]="vertical-align:"+y;return;case"font-color":case"mso-foreground":v[o++]="color:"+y;return;case"mso-background":case"mso-highlight":v[o++]="background:"+y;return;case"mso-default-height":v[o++]="min-height:"+x(y);return;case"mso-default-width":v[o++]="min-width:"+x(y);return;case"mso-padding-between-alt":v[o++]="border-collapse:separate;border-spacing:"+x(y);return;case"text-line-through":if((y=="single")||(y=="double")){v[o++]="text-decoration:line-through"}return;case"mso-zero-height":if(y=="yes"){v[o++]="display:none"}return}if(/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(w)){return}v[o++]=w+":"+z[1]}});if(o>0){return h+' style="'+v.join(";")+'"'}else{return h}}]])}}if(b(l,"paste_convert_headers_to_strong")){e([[/]*>/gi,"

      "],[/<\/h[1-6][^>]*>/gi,"

      "]])}j=b(l,"paste_strip_class_attributes");if(j!=="none"){function n(r,o){if(j==="all"){return""}var h=q(p(o.replace(/^(["'])(.*)\1$/,"$2")," "),function(s){return(/^(?!mso)/i.test(s))});return h.length?' class="'+h.join(" ")+'"':""}k=k.replace(/ class="([^"]+)"/gi,n);k=k.replace(/ class=(\w+)/gi,n)}if(b(l,"paste_remove_spans")){k=k.replace(/<\/?span[^>]*>/gi,"")}f.content=k},_postProcess:function(h,j){var g=this,f=g.editor,i=f.dom,e;if(j.wordContent){c(i.select("a",j.node),function(k){if(!k.href||k.href.indexOf("#_Toc")!=-1){i.remove(k,1)}});if(b(f,"paste_convert_middot_lists")){g._convertLists(h,j)}e=b(f,"paste_retain_style_properties");if((tinymce.is(e,"string"))&&(e!=="all")&&(e!=="*")){e=tinymce.explode(e.replace(/^none$/i,""));c(i.select("*",j.node),function(n){var o={},l=0,m,p,k;if(e){for(m=0;m0){i.setStyles(n,o)}else{if(n.nodeName=="SPAN"&&!n.className){i.remove(n,true)}}})}}if(b(f,"paste_remove_styles")||(b(f,"paste_remove_styles_if_webkit")&&tinymce.isWebKit)){c(i.select("*[style]",j.node),function(k){k.removeAttribute("style");k.removeAttribute("_mce_style")})}else{if(tinymce.isWebKit){c(i.select("*",j.node),function(k){k.removeAttribute("_mce_style")})}}},_convertLists:function(h,f){var j=h.editor.dom,i,m,e=-1,g,n=[],l,k;c(j.select("p",f.node),function(u){var r,v="",t,s,o,q;for(r=u.firstChild;r&&r.nodeType==3;r=r.nextSibling){v+=r.nodeValue}v=u.innerHTML.replace(/<\/?\w+[^>]*>/gi,"").replace(/ /g,"\u00a0");if(/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(v)){t="ul"}if(/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(v)){t="ol"}if(t){g=parseFloat(u.style.marginLeft||0);if(g>e){n.push(g)}if(!i||t!=l){i=j.create(t);j.insertAfter(i,u)}else{if(g>e){i=m.appendChild(j.create(t))}else{if(g]*>/gi,"");if(t=="ul"&&/^[\u2022\u00b7\u00a7\u00d8o]/.test(p)){j.remove(w)}else{if(/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(p)){j.remove(w)}}});s=u.innerHTML;if(t=="ul"){s=u.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/,"")}else{s=u.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^\s*\w+\.( |\u00a0)+\s*/,"")}m=i.appendChild(j.create("li",0,s));j.remove(u);e=g;l=t}else{i=e=0}});k=f.node.innerHTML;if(k.indexOf("__MCE_ITEM__")!=-1){f.node.innerHTML=k.replace(/__MCE_ITEM__/g,"")}},_insertBlockContent:function(l,h,m){var f,j,g=l.selection,q,n,e,o,i,k="mce_marker";function p(t){var s;if(tinymce.isIE){s=l.getDoc().body.createTextRange();s.moveToElementText(t);s.collapse(false);s.select()}else{g.select(t,1);g.collapse(false)}}this._insert('',1);j=h.get(k);f=h.getParent(j,"p,h1,h2,h3,h4,h5,h6,ul,ol,th,td");if(f&&!/TD|TH/.test(f.nodeName)){j=h.split(f,j);c(h.create("div",0,m).childNodes,function(r){q=j.parentNode.insertBefore(r.cloneNode(true),j)});p(q)}else{h.setOuterHTML(j,m);g.select(l.getBody(),1);g.collapse(0)}while(n=h.get(k)){h.remove(n)}n=g.getStart();e=h.getViewPort(l.getWin());o=l.dom.getPos(n).y;i=n.clientHeight;if(oe.y+e.h){l.getDoc().body.scrollTop=o0)){if(!d){d=("34,quot,38,amp,39,apos,60,lt,62,gt,"+j.serializer.settings.entities).split(",")}if(/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(v)){q([/[\n\r]+/g])}else{q([/\r+/g])}q([[/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi,"\n\n"],[/]*>|<\/tr>/gi,"\n"],[/<\/t[dh]>\s*]*>/gi,"\t"],/<[a-z!\/?][^>]*>/gi,[/ /gi," "],[/&(#\d+|[a-z0-9]{1,10});/gi,function(i,h){if(h.charAt(0)==="#"){return String.fromCharCode(h.slice(1))}else{return((i=y(d,h))>0)?String.fromCharCode(d[i-1]):" "}}],[/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi,"$1"],[/\n{3,}/g,"\n\n"],/^\s+|\s+$/g]);v=x.encode(v);if(!s.isCollapsed()){z.execCommand("Delete",false,null)}if(m(o,"array")||(m(o,"array"))){q(o)}else{if(m(o,"string")){q(new RegExp(o,"gi"))}}if(g=="none"){q([[/\n+/g," "]])}else{if(g=="br"){q([[/\n/g,"
      "]])}else{q([/^\s+|\s+$/g,[/\n\n/g,"

      "],[/\n/g,"
      "]])}}if((l=v.indexOf("

      "))!=-1){k=v.lastIndexOf("

      ");r=s.getNode();e=[];do{if(r.nodeType==1){if(r.nodeName=="TD"||r.nodeName=="BODY"){break}e[e.length]=r}}while(r=r.parentNode);if(e.length>0){p=v.substring(0,l);f="";for(t=0,u=e.length;t";f+="<"+e[e.length-t-1].nodeName.toLowerCase()+">"}if(l==k){v=p+f+v.substring(l+7)}else{v=p+v.substring(l+4,k+4)+f+v.substring(k+7)}}}j.execCommand("mceInsertRawHTML",false,v+' ');window.setTimeout(function(){var h=x.get("_plain_text_marker"),B,i,A,w;s.select(h,false);z.execCommand("Delete",false,null);h=null;B=s.getStart();i=x.getViewPort(n);A=x.getPos(B).y;w=B.clientHeight;if((Ai.y+i.h)){z.body.scrollTop=A

      ]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "

      $1

      "); + } + + if (getParam(ed, "paste_convert_middot_lists")) { + process([ + [//gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker + [/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol spans to item markers + ]); + } + + process([ + // Word comments like conditional comments etc + //gi, + + // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags + /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, + + // Convert into for line-though + [/<(\/?)s>/gi, "<$1strike>"], + + // Replace nsbp entites to char since it's easier to handle + [/ /gi, "\u00a0"] + ]); + + // Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag. + // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot. + do { + len = h.length; + h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); + } while (len != h.length); + + // Remove all spans if no styles is to be retained + if (getParam(ed, "paste_retain_style_properties").replace(/^none$/i, "").length == 0) { + h = h.replace(/<\/?span[^>]*>/gi, ""); + } else { + // We're keeping styles, so at least clean them up. + // CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx + + process([ + // Convert ___ to string of alternating breaking/non-breaking spaces of same length + [/([\s\u00a0]*)<\/span>/gi, + function(str, spaces) { + return (spaces.length > 0)? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ""; + } + ], + + // Examine all styles: delete junk, transform some, and keep the rest + [/(<[a-z][^>]*)\sstyle="([^"]*)"/gi, + function(str, tag, style) { + var n = [], + i = 0, + s = explode(trim(style).replace(/"/gi, "'"), ";"); + + // Examine each style definition within the tag's style attribute + each(s, function(v) { + var name, value, + parts = explode(v, ":"); + + function ensureUnits(v) { + return v + ((v !== "0") && (/\d$/.test(v)))? "px" : ""; + } + + if (parts.length == 2) { + name = parts[0].toLowerCase(); + value = parts[1].toLowerCase(); + + // Translate certain MS Office styles into their CSS equivalents + switch (name) { + case "mso-padding-alt": + case "mso-padding-top-alt": + case "mso-padding-right-alt": + case "mso-padding-bottom-alt": + case "mso-padding-left-alt": + case "mso-margin-alt": + case "mso-margin-top-alt": + case "mso-margin-right-alt": + case "mso-margin-bottom-alt": + case "mso-margin-left-alt": + case "mso-table-layout-alt": + case "mso-height": + case "mso-width": + case "mso-vertical-align-alt": + n[i++] = name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value); + return; + + case "horiz-align": + n[i++] = "text-align:" + value; + return; + + case "vert-align": + n[i++] = "vertical-align:" + value; + return; + + case "font-color": + case "mso-foreground": + n[i++] = "color:" + value; + return; + + case "mso-background": + case "mso-highlight": + n[i++] = "background:" + value; + return; + + case "mso-default-height": + n[i++] = "min-height:" + ensureUnits(value); + return; + + case "mso-default-width": + n[i++] = "min-width:" + ensureUnits(value); + return; + + case "mso-padding-between-alt": + n[i++] = "border-collapse:separate;border-spacing:" + ensureUnits(value); + return; + + case "text-line-through": + if ((value == "single") || (value == "double")) { + n[i++] = "text-decoration:line-through"; + } + return; + + case "mso-zero-height": + if (value == "yes") { + n[i++] = "display:none"; + } + return; + } + + // Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name + if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(name)) { + return; + } + + // If it reached this point, it must be a valid CSS style + n[i++] = name + ":" + parts[1]; // Lower-case name, but keep value case + } + }); + + // If style attribute contained any valid styles the re-write it; otherwise delete style attribute. + if (i > 0) { + return tag + ' style="' + n.join(';') + '"'; + } else { + return tag; + } + } + ] + ]); + } + } + + // Replace headers with + if (getParam(ed, "paste_convert_headers_to_strong")) { + process([ + [/]*>/gi, "

      "], + [/<\/h[1-6][^>]*>/gi, "

      "] + ]); + } + + // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso"). + // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation. + stripClass = getParam(ed, "paste_strip_class_attributes"); + + if (stripClass !== "none") { + function removeClasses(match, g1) { + if (stripClass === "all") + return ''; + + var cls = grep(explode(g1.replace(/^(["'])(.*)\1$/, "$2"), " "), + function(v) { + return (/^(?!mso)/i.test(v)); + } + ); + + return cls.length ? ' class="' + cls.join(" ") + '"' : ''; + }; + + h = h.replace(/ class="([^"]+)"/gi, removeClasses); + h = h.replace(/ class=(\w+)/gi, removeClasses); + } + + // Remove spans option + if (getParam(ed, "paste_remove_spans")) { + h = h.replace(/<\/?span[^>]*>/gi, ""); + } + + //console.log('After preprocess:' + h); + + o.content = h; + }, + + /** + * Various post process items. + */ + _postProcess : function(pl, o) { + var t = this, ed = t.editor, dom = ed.dom, styleProps; + + if (o.wordContent) { + // Remove named anchors or TOC links + each(dom.select('a', o.node), function(a) { + if (!a.href || a.href.indexOf('#_Toc') != -1) + dom.remove(a, 1); + }); + + if (getParam(ed, "paste_convert_middot_lists")) { + t._convertLists(pl, o); + } + + // Process styles + styleProps = getParam(ed, "paste_retain_style_properties"); // retained properties + + // Process only if a string was specified and not equal to "all" or "*" + if ((tinymce.is(styleProps, "string")) && (styleProps !== "all") && (styleProps !== "*")) { + styleProps = tinymce.explode(styleProps.replace(/^none$/i, "")); + + // Retains some style properties + each(dom.select('*', o.node), function(el) { + var newStyle = {}, npc = 0, i, sp, sv; + + // Store a subset of the existing styles + if (styleProps) { + for (i = 0; i < styleProps.length; i++) { + sp = styleProps[i]; + sv = dom.getStyle(el, sp); + + if (sv) { + newStyle[sp] = sv; + npc++; + } + } + } + + // Remove all of the existing styles + dom.setAttrib(el, 'style', ''); + + if (styleProps && npc > 0) + dom.setStyles(el, newStyle); // Add back the stored subset of styles + else // Remove empty span tags that do not have class attributes + if (el.nodeName == 'SPAN' && !el.className) + dom.remove(el, true); + }); + } + } + + // Remove all style information or only specifically on WebKit to avoid the style bug on that browser + if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) { + each(dom.select('*[style]', o.node), function(el) { + el.removeAttribute('style'); + el.removeAttribute('_mce_style'); + }); + } else { + if (tinymce.isWebKit) { + // We need to compress the styles on WebKit since if you paste it will become + // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles + each(dom.select('*', o.node), function(el) { + el.removeAttribute('_mce_style'); + }); + } + } + }, + + /** + * Converts the most common bullet and number formats in Office into a real semantic UL/LI list. + */ + _convertLists : function(pl, o) { + var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType, html; + + // Convert middot lists into real semantic lists + each(dom.select('p', o.node), function(p) { + var sib, val = '', type, html, idx, parents; + + // Get text node value at beginning of paragraph + for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling) + val += sib.nodeValue; + + val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0'); + + // Detect unordered lists look for bullets + if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(val)) + type = 'ul'; + + // Detect ordered lists 1., a. or ixv. + if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(val)) + type = 'ol'; + + // Check if node value matches the list pattern: o   + if (type) { + margin = parseFloat(p.style.marginLeft || 0); + + if (margin > lastMargin) + levels.push(margin); + + if (!listElm || type != lastType) { + listElm = dom.create(type); + dom.insertAfter(listElm, p); + } else { + // Nested list element + if (margin > lastMargin) { + listElm = li.appendChild(dom.create(type)); + } else if (margin < lastMargin) { + // Find parent level based on margin value + idx = tinymce.inArray(levels, margin); + parents = dom.getParents(listElm.parentNode, type); + listElm = parents[parents.length - 1 - idx] || listElm; + } + } + + // Remove middot or number spans if they exists + each(dom.select('span', p), function(span) { + var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, ''); + + // Remove span with the middot or the number + if (type == 'ul' && /^[\u2022\u00b7\u00a7\u00d8o]/.test(html)) + dom.remove(span); + else if (/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(html)) + dom.remove(span); + }); + + html = p.innerHTML; + + // Remove middot/list items + if (type == 'ul') + html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/, ''); + else + html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, ''); + + // Create li and add paragraph data into the new li + li = listElm.appendChild(dom.create('li', 0, html)); + dom.remove(p); + + lastMargin = margin; + lastType = type; + } else + listElm = lastMargin = 0; // End list element + }); + + // Remove any left over makers + html = o.node.innerHTML; + if (html.indexOf('__MCE_ITEM__') != -1) + o.node.innerHTML = html.replace(/__MCE_ITEM__/g, ''); + }, + + /** + * This method will split the current block parent and insert the contents inside the split position. + * This logic can be improved so text nodes at the start/end remain in the start/end block elements + */ + _insertBlockContent : function(ed, dom, content) { + var parentBlock, marker, sel = ed.selection, last, elm, vp, y, elmHeight, markerId = 'mce_marker'; + + function select(n) { + var r; + + if (tinymce.isIE) { + r = ed.getDoc().body.createTextRange(); + r.moveToElementText(n); + r.collapse(false); + r.select(); + } else { + sel.select(n, 1); + sel.collapse(false); + } + } + + // Insert a marker for the caret position + this._insert('', 1); + marker = dom.get(markerId); + parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol,th,td'); + + // If it's a parent block but not a table cell + if (parentBlock && !/TD|TH/.test(parentBlock.nodeName)) { + // Split parent block + marker = dom.split(parentBlock, marker); + + // Insert nodes before the marker + each(dom.create('div', 0, content).childNodes, function(n) { + last = marker.parentNode.insertBefore(n.cloneNode(true), marker); + }); + + // Move caret after marker + select(last); + } else { + dom.setOuterHTML(marker, content); + sel.select(ed.getBody(), 1); + sel.collapse(0); + } + + // Remove marker if it's left + while (elm = dom.get(markerId)) + dom.remove(elm); + + // Get element, position and height + elm = sel.getStart(); + vp = dom.getViewPort(ed.getWin()); + y = ed.dom.getPos(elm).y; + elmHeight = elm.clientHeight; + + // Is element within viewport if not then scroll it into view + if (y < vp.y || y + elmHeight > vp.y + vp.h) + ed.getDoc().body.scrollTop = y < vp.y ? y : y - vp.h + 25; + }, + + /** + * Inserts the specified contents at the caret position. + */ + _insert : function(h, skip_undo) { + var ed = this.editor, r = ed.selection.getRng(); + + // First delete the contents seems to work better on WebKit when the selection spans multiple list items or multiple table cells. + if (!ed.selection.isCollapsed() && r.startContainer != r.endContainer) + ed.getDoc().execCommand('Delete', false, null); + + // It's better to use the insertHTML method on Gecko since it will combine paragraphs correctly before inserting the contents + ed.execCommand(tinymce.isGecko ? 'insertHTML' : 'mceInsertContent', false, h, {skip_undo : skip_undo}); + }, + + /** + * Instead of the old plain text method which tried to re-create a paste operation, the + * new approach adds a plain text mode toggle switch that changes the behavior of paste. + * This function is passed the same input that the regular paste plugin produces. + * It performs additional scrubbing and produces (and inserts) the plain text. + * This approach leverages all of the great existing functionality in the paste + * plugin, and requires minimal changes to add the new functionality. + * Speednet - June 2009 + */ + _insertPlainText : function(ed, dom, h) { + var i, len, pos, rpos, node, breakElms, before, after, + w = ed.getWin(), + d = ed.getDoc(), + sel = ed.selection, + is = tinymce.is, + inArray = tinymce.inArray, + linebr = getParam(ed, "paste_text_linebreaktype"), + rl = getParam(ed, "paste_text_replacements"); + + function process(items) { + each(items, function(v) { + if (v.constructor == RegExp) + h = h.replace(v, ""); + else + h = h.replace(v[0], v[1]); + }); + }; + + if ((typeof(h) === "string") && (h.length > 0)) { + if (!entities) + entities = ("34,quot,38,amp,39,apos,60,lt,62,gt," + ed.serializer.settings.entities).split(","); + + // If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line + if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(h)) { + process([ + /[\n\r]+/g + ]); + } else { + // Otherwise just get rid of carriage returns (only need linefeeds) + process([ + /\r+/g + ]); + } + + process([ + [/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi, "\n\n"], // Block tags get a blank line after them + [/]*>|<\/tr>/gi, "\n"], // Single linebreak for
      tags and table rows + [/<\/t[dh]>\s*]*>/gi, "\t"], // Table cells get tabs betweem them + /<[a-z!\/?][^>]*>/gi, // Delete all remaining tags + [/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) + [ + // HTML entity + /&(#\d+|[a-z0-9]{1,10});/gi, + + // Replace with actual character + function(e, s) { + if (s.charAt(0) === "#") { + return String.fromCharCode(s.slice(1)); + } + else { + return ((e = inArray(entities, s)) > 0)? String.fromCharCode(entities[e-1]) : " "; + } + } + ], + [/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"], // Cool little RegExp deletes whitespace around linebreak chars. + [/\n{3,}/g, "\n\n"], // Max. 2 consecutive linebreaks + /^\s+|\s+$/g // Trim the front & back + ]); + + h = dom.encode(h); + + // Delete any highlighted text before pasting + if (!sel.isCollapsed()) { + d.execCommand("Delete", false, null); + } + + // Perform default or custom replacements + if (is(rl, "array") || (is(rl, "array"))) { + process(rl); + } + else if (is(rl, "string")) { + process(new RegExp(rl, "gi")); + } + + // Treat paragraphs as specified in the config + if (linebr == "none") { + process([ + [/\n+/g, " "] + ]); + } + else if (linebr == "br") { + process([ + [/\n/g, "
      "] + ]); + } + else { + process([ + /^\s+|\s+$/g, + [/\n\n/g, "

      "], + [/\n/g, "
      "] + ]); + } + + // This next piece of code handles the situation where we're pasting more than one paragraph of plain + // text, and we are pasting the content into the middle of a block node in the editor. The block + // node gets split at the selection point into "Para A" and "Para B" (for the purposes of explaining). + // The first paragraph of the pasted text is appended to "Para A", and the last paragraph of the + // pasted text is prepended to "Para B". Any other paragraphs of pasted text are placed between + // "Para A" and "Para B". This code solves a host of problems with the original plain text plugin and + // now handles styles correctly. (Pasting plain text into a styled paragraph is supposed to make the + // plain text take the same style as the existing paragraph.) + if ((pos = h.indexOf("

      ")) != -1) { + rpos = h.lastIndexOf("

      "); + node = sel.getNode(); + breakElms = []; // Get list of elements to break + + do { + if (node.nodeType == 1) { + // Don't break tables and break at body + if (node.nodeName == "TD" || node.nodeName == "BODY") { + break; + } + + breakElms[breakElms.length] = node; + } + } while (node = node.parentNode); + + // Are we in the middle of a block node? + if (breakElms.length > 0) { + before = h.substring(0, pos); + after = ""; + + for (i=0, len=breakElms.length; i"; + after += "<" + breakElms[breakElms.length-i-1].nodeName.toLowerCase() + ">"; + } + + if (pos == rpos) { + h = before + after + h.substring(pos+7); + } + else { + h = before + h.substring(pos+4, rpos+4) + after + h.substring(rpos+7); + } + } + } + + // Insert content at the caret, plus add a marker for repositioning the caret + ed.execCommand("mceInsertRawHTML", false, h + ' '); + + // Reposition the caret to the marker, which was placed immediately after the inserted content. + // Needs to be done asynchronously (in window.setTimeout) or else it doesn't work in all browsers. + // The second part of the code scrolls the content up if the caret is positioned off-screen. + // This is only necessary for WebKit browsers, but it doesn't hurt to use for all. + window.setTimeout(function() { + var marker = dom.get('_plain_text_marker'), + elm, vp, y, elmHeight; + + sel.select(marker, false); + d.execCommand("Delete", false, null); + marker = null; + + // Get element, position and height + elm = sel.getStart(); + vp = dom.getViewPort(w); + y = dom.getPos(elm).y; + elmHeight = elm.clientHeight; + + // Is element within viewport if not then scroll it into view + if ((y < vp.y) || (y + elmHeight > vp.y + vp.h)) { + d.body.scrollTop = y < vp.y ? y : y - vp.h + 25; + } + }, 0); + } + }, + + /** + * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine. + */ + _legacySupport : function() { + var t = this, ed = t.editor; + + // Register command(s) for backwards compatibility + ed.addCommand("mcePasteWord", function() { + ed.windowManager.open({ + file: t.url + "/pasteword.htm", + width: parseInt(getParam(ed, "paste_dialog_width")), + height: parseInt(getParam(ed, "paste_dialog_height")), + inline: 1 + }); + }); + + if (getParam(ed, "paste_text_use_dialog")) { + ed.addCommand("mcePasteText", function() { + ed.windowManager.open({ + file : t.url + "/pastetext.htm", + width: parseInt(getParam(ed, "paste_dialog_width")), + height: parseInt(getParam(ed, "paste_dialog_height")), + inline : 1 + }); + }); + } + + // Register button for backwards compatibility + ed.addButton("pasteword", {title : "paste.paste_word_desc", cmd : "mcePasteWord"}); + } + }); + + // Register plugin + tinymce.PluginManager.add("paste", tinymce.plugins.PastePlugin); +})(); diff --git a/web/js/globals/tinymce/plugins/paste/js/pastetext.js b/web/js/globals/tinymce/plugins/paste/js/pastetext.js new file mode 100644 index 0000000..c524f9e --- /dev/null +++ b/web/js/globals/tinymce/plugins/paste/js/pastetext.js @@ -0,0 +1,36 @@ +tinyMCEPopup.requireLangPack(); + +var PasteTextDialog = { + init : function() { + this.resize(); + }, + + insert : function() { + var h = tinyMCEPopup.dom.encode(document.getElementById('content').value), lines; + + // Convert linebreaks into paragraphs + if (document.getElementById('linebreaks').checked) { + lines = h.split(/\r?\n/); + if (lines.length > 1) { + h = ''; + tinymce.each(lines, function(row) { + h += '

      ' + row + '

      '; + }); + } + } + + tinyMCEPopup.editor.execCommand('mceInsertClipboardContent', false, {content : h}); + tinyMCEPopup.close(); + }, + + resize : function() { + var vp = tinyMCEPopup.dom.getViewPort(window), el; + + el = document.getElementById('content'); + + el.style.width = (vp.w - 20) + 'px'; + el.style.height = (vp.h - 90) + 'px'; + } +}; + +tinyMCEPopup.onInit.add(PasteTextDialog.init, PasteTextDialog); diff --git a/web/js/globals/tinymce/plugins/paste/js/pasteword.js b/web/js/globals/tinymce/plugins/paste/js/pasteword.js new file mode 100644 index 0000000..a52731c --- /dev/null +++ b/web/js/globals/tinymce/plugins/paste/js/pasteword.js @@ -0,0 +1,51 @@ +tinyMCEPopup.requireLangPack(); + +var PasteWordDialog = { + init : function() { + var ed = tinyMCEPopup.editor, el = document.getElementById('iframecontainer'), ifr, doc, css, cssHTML = ''; + + // Create iframe + el.innerHTML = ''; + ifr = document.getElementById('iframe'); + doc = ifr.contentWindow.document; + + // Force absolute CSS urls + css = [ed.baseURI.toAbsolute("themes/" + ed.settings.theme + "/skins/" + ed.settings.skin + "/content.css")]; + css = css.concat(tinymce.explode(ed.settings.content_css) || []); + tinymce.each(css, function(u) { + cssHTML += ''; + }); + + // Write content into iframe + doc.open(); + doc.write('' + cssHTML + ''); + doc.close(); + + doc.designMode = 'on'; + this.resize(); + + window.setTimeout(function() { + ifr.contentWindow.focus(); + }, 10); + }, + + insert : function() { + var h = document.getElementById('iframe').contentWindow.document.body.innerHTML; + + tinyMCEPopup.editor.execCommand('mceInsertClipboardContent', false, {content : h, wordContent : true}); + tinyMCEPopup.close(); + }, + + resize : function() { + var vp = tinyMCEPopup.dom.getViewPort(window), el; + + el = document.getElementById('iframe'); + + if (el) { + el.style.width = (vp.w - 20) + 'px'; + el.style.height = (vp.h - 90) + 'px'; + } + } +}; + +tinyMCEPopup.onInit.add(PasteWordDialog.init, PasteWordDialog); diff --git a/web/js/globals/tinymce/plugins/paste/langs/en_dlg.js b/web/js/globals/tinymce/plugins/paste/langs/en_dlg.js new file mode 100644 index 0000000..eeac778 --- /dev/null +++ b/web/js/globals/tinymce/plugins/paste/langs/en_dlg.js @@ -0,0 +1,5 @@ +tinyMCE.addI18n('en.paste_dlg',{ +text_title:"Use CTRL+V on your keyboard to paste the text into the window.", +text_linebreaks:"Keep linebreaks", +word_title:"Use CTRL+V on your keyboard to paste the text into the window." +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/paste/pastetext.htm b/web/js/globals/tinymce/plugins/paste/pastetext.htm new file mode 100644 index 0000000..b655945 --- /dev/null +++ b/web/js/globals/tinymce/plugins/paste/pastetext.htm @@ -0,0 +1,27 @@ + + + {#paste.paste_text_desc} + + + + +
      +
      {#paste.paste_text_desc}
      + +
      + +
      + +
      + +
      {#paste_dlg.text_title}
      + + + +
      + + +
      +
      + + \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/paste/pasteword.htm b/web/js/globals/tinymce/plugins/paste/pasteword.htm new file mode 100644 index 0000000..0f6bb41 --- /dev/null +++ b/web/js/globals/tinymce/plugins/paste/pasteword.htm @@ -0,0 +1,21 @@ + + + {#paste.paste_word_desc} + + + + +
      +
      {#paste.paste_word_desc}
      + +
      {#paste_dlg.word_title}
      + +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/preview/editor_plugin.js b/web/js/globals/tinymce/plugins/preview/editor_plugin.js new file mode 100644 index 0000000..507909c --- /dev/null +++ b/web/js/globals/tinymce/plugins/preview/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.Preview",{init:function(a,b){var d=this,c=tinymce.explode(a.settings.content_css);d.editor=a;tinymce.each(c,function(f,e){c[e]=a.documentBaseURI.toAbsolute(f)});a.addCommand("mcePreview",function(){a.windowManager.open({file:a.getParam("plugin_preview_pageurl",b+"/preview.html"),width:parseInt(a.getParam("plugin_preview_width","550")),height:parseInt(a.getParam("plugin_preview_height","600")),resizable:"yes",scrollbars:"yes",popup_css:c?c.join(","):a.baseURI.toAbsolute("themes/"+a.settings.theme+"/skins/"+a.settings.skin+"/content.css"),inline:a.getParam("plugin_preview_inline",1)},{base:a.documentBaseURI.getURI()})});a.addButton("preview",{title:"preview.preview_desc",cmd:"mcePreview"})},getInfo:function(){return{longname:"Preview",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/preview",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("preview",tinymce.plugins.Preview)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/preview/editor_plugin_src.js b/web/js/globals/tinymce/plugins/preview/editor_plugin_src.js new file mode 100644 index 0000000..80f00f0 --- /dev/null +++ b/web/js/globals/tinymce/plugins/preview/editor_plugin_src.js @@ -0,0 +1,53 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Preview', { + init : function(ed, url) { + var t = this, css = tinymce.explode(ed.settings.content_css); + + t.editor = ed; + + // Force absolute CSS urls + tinymce.each(css, function(u, k) { + css[k] = ed.documentBaseURI.toAbsolute(u); + }); + + ed.addCommand('mcePreview', function() { + ed.windowManager.open({ + file : ed.getParam("plugin_preview_pageurl", url + "/preview.html"), + width : parseInt(ed.getParam("plugin_preview_width", "550")), + height : parseInt(ed.getParam("plugin_preview_height", "600")), + resizable : "yes", + scrollbars : "yes", + popup_css : css ? css.join(',') : ed.baseURI.toAbsolute("themes/" + ed.settings.theme + "/skins/" + ed.settings.skin + "/content.css"), + inline : ed.getParam("plugin_preview_inline", 1) + }, { + base : ed.documentBaseURI.getURI() + }); + }); + + ed.addButton('preview', {title : 'preview.preview_desc', cmd : 'mcePreview'}); + }, + + getInfo : function() { + return { + longname : 'Preview', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/preview', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('preview', tinymce.plugins.Preview); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/preview/example.html b/web/js/globals/tinymce/plugins/preview/example.html new file mode 100644 index 0000000..b2c3d90 --- /dev/null +++ b/web/js/globals/tinymce/plugins/preview/example.html @@ -0,0 +1,28 @@ + + + + + +Example of a custom preview page + + + +Editor contents:
      +
      + +
      + + + diff --git a/web/js/globals/tinymce/plugins/preview/jscripts/embed.js b/web/js/globals/tinymce/plugins/preview/jscripts/embed.js new file mode 100644 index 0000000..f8dc810 --- /dev/null +++ b/web/js/globals/tinymce/plugins/preview/jscripts/embed.js @@ -0,0 +1,73 @@ +/** + * This script contains embed functions for common plugins. This scripts are complety free to use for any purpose. + */ + +function writeFlash(p) { + writeEmbed( + 'D27CDB6E-AE6D-11cf-96B8-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'application/x-shockwave-flash', + p + ); +} + +function writeShockWave(p) { + writeEmbed( + '166B1BCA-3F9C-11CF-8075-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0', + 'application/x-director', + p + ); +} + +function writeQuickTime(p) { + writeEmbed( + '02BF25D5-8C17-4B23-BC80-D3488ABDDC6B', + 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0', + 'video/quicktime', + p + ); +} + +function writeRealMedia(p) { + writeEmbed( + 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'audio/x-pn-realaudio-plugin', + p + ); +} + +function writeWindowsMedia(p) { + p.url = p.src; + writeEmbed( + '6BF52A52-394A-11D3-B153-00C04F79FAA6', + 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701', + 'application/x-mplayer2', + p + ); +} + +function writeEmbed(cls, cb, mt, p) { + var h = '', n; + + h += ''; + + h += ' + + + + + +{#preview.preview_desc} + + + + + diff --git a/web/js/globals/tinymce/plugins/print/editor_plugin.js b/web/js/globals/tinymce/plugins/print/editor_plugin.js new file mode 100644 index 0000000..b5b3a55 --- /dev/null +++ b/web/js/globals/tinymce/plugins/print/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.Print",{init:function(a,b){a.addCommand("mcePrint",function(){a.getWin().print()});a.addButton("print",{title:"print.print_desc",cmd:"mcePrint"})},getInfo:function(){return{longname:"Print",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/print",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("print",tinymce.plugins.Print)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/print/editor_plugin_src.js b/web/js/globals/tinymce/plugins/print/editor_plugin_src.js new file mode 100644 index 0000000..3933fe6 --- /dev/null +++ b/web/js/globals/tinymce/plugins/print/editor_plugin_src.js @@ -0,0 +1,34 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Print', { + init : function(ed, url) { + ed.addCommand('mcePrint', function() { + ed.getWin().print(); + }); + + ed.addButton('print', {title : 'print.print_desc', cmd : 'mcePrint'}); + }, + + getInfo : function() { + return { + longname : 'Print', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/print', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('print', tinymce.plugins.Print); +})(); diff --git a/web/js/globals/tinymce/plugins/save/editor_plugin.js b/web/js/globals/tinymce/plugins/save/editor_plugin.js new file mode 100644 index 0000000..8e93996 --- /dev/null +++ b/web/js/globals/tinymce/plugins/save/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.Save",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceSave",c._save,c);a.addCommand("mceCancel",c._cancel,c);a.addButton("save",{title:"save.save_desc",cmd:"mceSave"});a.addButton("cancel",{title:"save.cancel_desc",cmd:"mceCancel"});a.onNodeChange.add(c._nodeChange,c);a.addShortcut("ctrl+s",a.getLang("save.save_desc"),"mceSave")},getInfo:function(){return{longname:"Save",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/save",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_nodeChange:function(b,a,c){var b=this.editor;if(b.getParam("save_enablewhendirty")){a.setDisabled("save",!b.isDirty());a.setDisabled("cancel",!b.isDirty())}},_save:function(){var c=this.editor,a,e,d,b;a=tinymce.DOM.get(c.id).form||tinymce.DOM.getParent(c.id,"form");if(c.getParam("save_enablewhendirty")&&!c.isDirty()){return}tinyMCE.triggerSave();if(e=c.getParam("save_onsavecallback")){if(c.execCallback("save_onsavecallback",c)){c.startContent=tinymce.trim(c.getContent({format:"raw"}));c.nodeChanged()}return}if(a){c.isNotDirty=true;if(a.onsubmit==null||a.onsubmit()!=false){a.submit()}c.nodeChanged()}else{c.windowManager.alert("Error: No form element found.")}},_cancel:function(){var a=this.editor,c,b=tinymce.trim(a.startContent);if(c=a.getParam("save_oncancelcallback")){a.execCallback("save_oncancelcallback",a);return}a.setContent(b);a.undoManager.clear();a.nodeChanged()}});tinymce.PluginManager.add("save",tinymce.plugins.Save)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/save/editor_plugin_src.js b/web/js/globals/tinymce/plugins/save/editor_plugin_src.js new file mode 100644 index 0000000..f5a3de8 --- /dev/null +++ b/web/js/globals/tinymce/plugins/save/editor_plugin_src.js @@ -0,0 +1,101 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Save', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceSave', t._save, t); + ed.addCommand('mceCancel', t._cancel, t); + + // Register buttons + ed.addButton('save', {title : 'save.save_desc', cmd : 'mceSave'}); + ed.addButton('cancel', {title : 'save.cancel_desc', cmd : 'mceCancel'}); + + ed.onNodeChange.add(t._nodeChange, t); + ed.addShortcut('ctrl+s', ed.getLang('save.save_desc'), 'mceSave'); + }, + + getInfo : function() { + return { + longname : 'Save', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/save', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + _nodeChange : function(ed, cm, n) { + var ed = this.editor; + + if (ed.getParam('save_enablewhendirty')) { + cm.setDisabled('save', !ed.isDirty()); + cm.setDisabled('cancel', !ed.isDirty()); + } + }, + + // Private methods + + _save : function() { + var ed = this.editor, formObj, os, i, elementId; + + formObj = tinymce.DOM.get(ed.id).form || tinymce.DOM.getParent(ed.id, 'form'); + + if (ed.getParam("save_enablewhendirty") && !ed.isDirty()) + return; + + tinyMCE.triggerSave(); + + // Use callback instead + if (os = ed.getParam("save_onsavecallback")) { + if (ed.execCallback('save_onsavecallback', ed)) { + ed.startContent = tinymce.trim(ed.getContent({format : 'raw'})); + ed.nodeChanged(); + } + + return; + } + + if (formObj) { + ed.isNotDirty = true; + + if (formObj.onsubmit == null || formObj.onsubmit() != false) + formObj.submit(); + + ed.nodeChanged(); + } else + ed.windowManager.alert("Error: No form element found."); + }, + + _cancel : function() { + var ed = this.editor, os, h = tinymce.trim(ed.startContent); + + // Use callback instead + if (os = ed.getParam("save_oncancelcallback")) { + ed.execCallback('save_oncancelcallback', ed); + return; + } + + ed.setContent(h); + ed.undoManager.clear(); + ed.nodeChanged(); + } + }); + + // Register plugin + tinymce.PluginManager.add('save', tinymce.plugins.Save); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/searchreplace/css/searchreplace.css b/web/js/globals/tinymce/plugins/searchreplace/css/searchreplace.css new file mode 100644 index 0000000..ecdf58c --- /dev/null +++ b/web/js/globals/tinymce/plugins/searchreplace/css/searchreplace.css @@ -0,0 +1,6 @@ +.panel_wrapper {height:85px;} +.panel_wrapper div.current {height:85px;} + +/* IE */ +* html .panel_wrapper {height:100px;} +* html .panel_wrapper div.current {height:100px;} diff --git a/web/js/globals/tinymce/plugins/searchreplace/editor_plugin.js b/web/js/globals/tinymce/plugins/searchreplace/editor_plugin.js new file mode 100644 index 0000000..cd9c985 --- /dev/null +++ b/web/js/globals/tinymce/plugins/searchreplace/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.SearchReplacePlugin",{init:function(a,c){function b(d){a.windowManager.open({file:c+"/searchreplace.htm",width:420+parseInt(a.getLang("searchreplace.delta_width",0)),height:170+parseInt(a.getLang("searchreplace.delta_height",0)),inline:1,auto_focus:0},{mode:d,search_string:a.selection.getContent({format:"text"}),plugin_url:c})}a.addCommand("mceSearch",function(){b("search")});a.addCommand("mceReplace",function(){b("replace")});a.addButton("search",{title:"searchreplace.search_desc",cmd:"mceSearch"});a.addButton("replace",{title:"searchreplace.replace_desc",cmd:"mceReplace"});a.addShortcut("ctrl+f","searchreplace.search_desc","mceSearch")},getInfo:function(){return{longname:"Search/Replace",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("searchreplace",tinymce.plugins.SearchReplacePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/searchreplace/editor_plugin_src.js b/web/js/globals/tinymce/plugins/searchreplace/editor_plugin_src.js new file mode 100644 index 0000000..1433a06 --- /dev/null +++ b/web/js/globals/tinymce/plugins/searchreplace/editor_plugin_src.js @@ -0,0 +1,57 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.SearchReplacePlugin', { + init : function(ed, url) { + function open(m) { + ed.windowManager.open({ + file : url + '/searchreplace.htm', + width : 420 + parseInt(ed.getLang('searchreplace.delta_width', 0)), + height : 170 + parseInt(ed.getLang('searchreplace.delta_height', 0)), + inline : 1, + auto_focus : 0 + }, { + mode : m, + search_string : ed.selection.getContent({format : 'text'}), + plugin_url : url + }); + }; + + // Register commands + ed.addCommand('mceSearch', function() { + open('search'); + }); + + ed.addCommand('mceReplace', function() { + open('replace'); + }); + + // Register buttons + ed.addButton('search', {title : 'searchreplace.search_desc', cmd : 'mceSearch'}); + ed.addButton('replace', {title : 'searchreplace.replace_desc', cmd : 'mceReplace'}); + + ed.addShortcut('ctrl+f', 'searchreplace.search_desc', 'mceSearch'); + }, + + getInfo : function() { + return { + longname : 'Search/Replace', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('searchreplace', tinymce.plugins.SearchReplacePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/searchreplace/js/searchreplace.js b/web/js/globals/tinymce/plugins/searchreplace/js/searchreplace.js new file mode 100644 index 0000000..0137ba0 --- /dev/null +++ b/web/js/globals/tinymce/plugins/searchreplace/js/searchreplace.js @@ -0,0 +1,138 @@ +tinyMCEPopup.requireLangPack(); + +var SearchReplaceDialog = { + init : function(ed) { + var f = document.forms[0], m = tinyMCEPopup.getWindowArg("mode"); + + this.switchMode(m); + + f[m + '_panel_searchstring'].value = tinyMCEPopup.getWindowArg("search_string"); + + // Focus input field + f[m + '_panel_searchstring'].focus(); + }, + + switchMode : function(m) { + var f, lm = this.lastMode; + + if (lm != m) { + f = document.forms[0]; + + if (lm) { + f[m + '_panel_searchstring'].value = f[lm + '_panel_searchstring'].value; + f[m + '_panel_backwardsu'].checked = f[lm + '_panel_backwardsu'].checked; + f[m + '_panel_backwardsd'].checked = f[lm + '_panel_backwardsd'].checked; + f[m + '_panel_casesensitivebox'].checked = f[lm + '_panel_casesensitivebox'].checked; + } + + mcTabs.displayTab(m + '_tab', m + '_panel'); + document.getElementById("replaceBtn").style.display = (m == "replace") ? "inline" : "none"; + document.getElementById("replaceAllBtn").style.display = (m == "replace") ? "inline" : "none"; + this.lastMode = m; + } + }, + + searchNext : function(a) { + var ed = tinyMCEPopup.editor, se = ed.selection, r = se.getRng(), f, m = this.lastMode, s, b, fl = 0, w = ed.getWin(), wm = ed.windowManager, fo = 0; + + // Get input + f = document.forms[0]; + s = f[m + '_panel_searchstring'].value; + b = f[m + '_panel_backwardsu'].checked; + ca = f[m + '_panel_casesensitivebox'].checked; + rs = f['replace_panel_replacestring'].value; + + if (tinymce.isIE) { + r = ed.getDoc().selection.createRange(); + } + + if (s == '') + return; + + function fix() { + // Correct Firefox graphics glitches + r = se.getRng().cloneRange(); + ed.getDoc().execCommand('SelectAll', false, null); + se.setRng(r); + }; + + function replace() { + if (tinymce.isIE) + ed.selection.getRng().duplicate().pasteHTML(rs); // Needs to be duplicated due to selection bug in IE + else + ed.getDoc().execCommand('InsertHTML', false, rs); + }; + + // IE flags + if (ca) + fl = fl | 4; + + switch (a) { + case 'all': + // Move caret to beginning of text + ed.execCommand('SelectAll'); + ed.selection.collapse(true); + + if (tinymce.isIE) { + while (r.findText(s, b ? -1 : 1, fl)) { + r.scrollIntoView(); + r.select(); + replace(); + fo = 1; + + if (b) { + r.moveEnd("character", -(rs.length)); // Otherwise will loop forever + } + } + + tinyMCEPopup.storeSelection(); + } else { + while (w.find(s, ca, b, false, false, false, false)) { + replace(); + fo = 1; + } + } + + if (fo) + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.allreplaced')); + else + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + + return; + + case 'current': + if (!ed.selection.isCollapsed()) + replace(); + + break; + } + + se.collapse(b); + r = se.getRng(); + + if (tinymce.isIE) { + r = ed.getDoc().selection.createRange(); + } + + // Whats the point + if (!s) + return; + + if (tinymce.isIE) { + if (r.findText(s, b ? -1 : 1, fl)) { + r.scrollIntoView(); + r.select(); + } else + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + + tinyMCEPopup.storeSelection(); + } else { + if (!w.find(s, ca, b, false, false, false, false)) + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + else + fix(); + } + } +}; + +tinyMCEPopup.onInit.add(SearchReplaceDialog.init, SearchReplaceDialog); diff --git a/web/js/globals/tinymce/plugins/searchreplace/langs/en_dlg.js b/web/js/globals/tinymce/plugins/searchreplace/langs/en_dlg.js new file mode 100644 index 0000000..370959a --- /dev/null +++ b/web/js/globals/tinymce/plugins/searchreplace/langs/en_dlg.js @@ -0,0 +1,16 @@ +tinyMCE.addI18n('en.searchreplace_dlg',{ +searchnext_desc:"Find again", +notfound:"The search has been completed. The search string could not be found.", +search_title:"Find", +replace_title:"Find/Replace", +allreplaced:"All occurrences of the search string were replaced.", +findwhat:"Find what", +replacewith:"Replace with", +direction:"Direction", +up:"Up", +down:"Down", +mcase:"Match case", +findnext:"Find next", +replace:"Replace", +replaceall:"Replace all" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/searchreplace/searchreplace.htm b/web/js/globals/tinymce/plugins/searchreplace/searchreplace.htm new file mode 100644 index 0000000..d0424cf --- /dev/null +++ b/web/js/globals/tinymce/plugins/searchreplace/searchreplace.htm @@ -0,0 +1,99 @@ + + + + {#searchreplace_dlg.replace_title} + + + + + + + +
      + + +
      +
      + + + + + + + + + + + +
      + + + + + + + + +
      +
      + + + + + +
      +
      +
      + +
      + + + + + + + + + + + + + + + +
      + + + + + + + + +
      +
      + + + + + +
      +
      +
      + +
      + +
      + + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/spellchecker/css/content.css b/web/js/globals/tinymce/plugins/spellchecker/css/content.css new file mode 100644 index 0000000..24efa02 --- /dev/null +++ b/web/js/globals/tinymce/plugins/spellchecker/css/content.css @@ -0,0 +1 @@ +.mceItemHiddenSpellWord {background:url(../img/wline.gif) repeat-x bottom left; cursor:default;} diff --git a/web/js/globals/tinymce/plugins/spellchecker/editor_plugin.js b/web/js/globals/tinymce/plugins/spellchecker/editor_plugin.js new file mode 100644 index 0000000..a9ec3b9 --- /dev/null +++ b/web/js/globals/tinymce/plugins/spellchecker/editor_plugin.js @@ -0,0 +1 @@ +(function(){var a=tinymce.util.JSONRequest,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.SpellcheckerPlugin",{getInfo:function(){return{longname:"Spellchecker",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker",version:tinymce.majorVersion+"."+tinymce.minorVersion}},init:function(e,f){var g=this,d;g.url=f;g.editor=e;g.rpcUrl=e.getParam("spellchecker_rpc_url","{backend}");if(g.rpcUrl=="{backend}"){if(tinymce.isIE){return}g.hasSupport=true;e.onContextMenu.addToTop(function(h,i){if(g.active){return false}})}e.addCommand("mceSpellCheck",function(){if(g.rpcUrl=="{backend}"){g.editor.getBody().spellcheck=g.active=!g.active;return}if(!g.active){e.setProgressState(1);g._sendRPC("checkWords",[g.selectedLang,g._getWords()],function(h){if(h.length>0){g.active=1;g._markWords(h);e.setProgressState(0);e.nodeChanged()}else{e.setProgressState(0);if(e.getParam("spellchecker_report_no_misspellings",true)){e.windowManager.alert("spellchecker.no_mpell")}}})}else{g._done()}});e.onInit.add(function(){if(e.settings.content_css!==false){e.dom.loadCSS(f+"/css/content.css")}});e.onClick.add(g._showMenu,g);e.onContextMenu.add(g._showMenu,g);e.onBeforeGetContent.add(function(){if(g.active){g._removeWords()}});e.onNodeChange.add(function(i,h){h.setActive("spellchecker",g.active)});e.onSetContent.add(function(){g._done()});e.onBeforeGetContent.add(function(){g._done()});e.onBeforeExecCommand.add(function(h,i){if(i=="mceFullScreen"){g._done()}});g.languages={};c(e.getParam("spellchecker_languages","+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv","hash"),function(i,h){if(h.indexOf("+")===0){h=h.substring(1);g.selectedLang=i}g.languages[h]=i})},createControl:function(h,d){var f=this,g,e=f.editor;if(h=="spellchecker"){if(f.rpcUrl=="{backend}"){if(f.hasSupport){g=d.createButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f})}return g}g=d.createSplitButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f});g.onRenderMenu.add(function(j,i){i.add({title:"spellchecker.langs","class":"mceMenuItemTitle"}).setDisabled(1);c(f.languages,function(n,m){var p={icon:1},l;p.onclick=function(){l.setSelected(1);f.selectedItem.setSelected(0);f.selectedItem=l;f.selectedLang=n};p.title=m;l=i.add(p);l.setSelected(n==f.selectedLang);if(n==f.selectedLang){f.selectedItem=l}})});return g}},_walk:function(i,g){var h=this.editor.getDoc(),e;if(h.createTreeWalker){e=h.createTreeWalker(i,NodeFilter.SHOW_TEXT,null,false);while((i=e.nextNode())!=null){g.call(this,i)}}else{tinymce.walk(i,g,"childNodes")}},_getSeparators:function(){var e="",d,f=this.editor.getParam("spellchecker_word_separator_chars",'\\s!"#$%&()*+,-./:;<=>?@[]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c');for(d=0;d$1$2');q=q.replace(g,'$1$2');j.replace(j.create("span",{"class":"mceItemHidden"},q),r)}}});l.moveToBookmark(m)},_showMenu:function(h,j){var i=this,h=i.editor,d=i._menu,l,k=h.dom,g=k.getViewPort(h.getWin()),f=j.target;j=0;if(!d){l=b.getPos(h.getContentAreaContainer());d=h.controlManager.createDropMenu("spellcheckermenu",{offset_x:l.x,offset_y:l.y,"class":"mceNoIcons"});i._menu=d}if(k.hasClass(f,"mceItemHiddenSpellWord")){d.removeAll();d.add({title:"spellchecker.wait","class":"mceMenuItemTitle"}).setDisabled(1);i._sendRPC("getSuggestions",[i.selectedLang,k.decode(f.innerHTML)],function(m){var e;d.removeAll();if(m.length>0){d.add({title:"spellchecker.sug","class":"mceMenuItemTitle"}).setDisabled(1);c(m,function(n){d.add({title:n,onclick:function(){k.replace(h.getDoc().createTextNode(n),f);i._checkDone()}})});d.addSeparator()}else{d.add({title:"spellchecker.no_sug","class":"mceMenuItemTitle"}).setDisabled(1)}e=i.editor.getParam("spellchecker_enable_ignore_rpc","");d.add({title:"spellchecker.ignore_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}}});d.add({title:"spellchecker.ignore_words",onclick:function(){var n=f.innerHTML;i._removeWords(k.decode(n));i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWords",[i.selectedLang,n],function(o){h.setProgressState(0)})}}});if(i.editor.getParam("spellchecker_enable_learn_rpc")){d.add({title:"spellchecker.learn_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();h.setProgressState(1);i._sendRPC("learnWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}})}d.update()});h.selection.select(f);l=k.getPos(f);d.showMenu(l.x,l.y+f.offsetHeight-g.y);return tinymce.dom.Event.cancel(j)}else{d.hideMenu()}},_checkDone:function(){var e=this,d=e.editor,g=d.dom,f;c(g.select("span"),function(h){if(h&&g.hasClass(h,"mceItemHiddenSpellWord")){f=true;return false}});if(!f){e._done()}},_done:function(){var d=this,e=d.active;if(d.active){d.active=0;d._removeWords();if(d._menu){d._menu.hideMenu()}if(e){d.editor.nodeChanged()}}},_sendRPC:function(e,g,d){var f=this;a.sendRPC({url:f.rpcUrl,method:e,params:g,success:d,error:function(i,h){f.editor.setProgressState(0);f.editor.windowManager.alert(i.errstr||("Error response: "+h.responseText))}})}});tinymce.PluginManager.add("spellchecker",tinymce.plugins.SpellcheckerPlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/spellchecker/editor_plugin_src.js b/web/js/globals/tinymce/plugins/spellchecker/editor_plugin_src.js new file mode 100644 index 0000000..d8680ba --- /dev/null +++ b/web/js/globals/tinymce/plugins/spellchecker/editor_plugin_src.js @@ -0,0 +1,417 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var JSONRequest = tinymce.util.JSONRequest, each = tinymce.each, DOM = tinymce.DOM; + + tinymce.create('tinymce.plugins.SpellcheckerPlugin', { + getInfo : function() { + return { + longname : 'Spellchecker', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + init : function(ed, url) { + var t = this, cm; + + t.url = url; + t.editor = ed; + t.rpcUrl = ed.getParam("spellchecker_rpc_url", "{backend}"); + + if (t.rpcUrl == '{backend}') { + // Sniff if the browser supports native spellchecking (Don't know of a better way) + if (tinymce.isIE) + return; + + t.hasSupport = true; + + // Disable the context menu when spellchecking is active + ed.onContextMenu.addToTop(function(ed, e) { + if (t.active) + return false; + }); + } + + // Register commands + ed.addCommand('mceSpellCheck', function() { + if (t.rpcUrl == '{backend}') { + // Enable/disable native spellchecker + t.editor.getBody().spellcheck = t.active = !t.active; + return; + } + + if (!t.active) { + ed.setProgressState(1); + t._sendRPC('checkWords', [t.selectedLang, t._getWords()], function(r) { + if (r.length > 0) { + t.active = 1; + t._markWords(r); + ed.setProgressState(0); + ed.nodeChanged(); + } else { + ed.setProgressState(0); + + if (ed.getParam('spellchecker_report_no_misspellings', true)) + ed.windowManager.alert('spellchecker.no_mpell'); + } + }); + } else + t._done(); + }); + + ed.onInit.add(function() { + if (ed.settings.content_css !== false) + ed.dom.loadCSS(url + '/css/content.css'); + }); + + ed.onClick.add(t._showMenu, t); + ed.onContextMenu.add(t._showMenu, t); + ed.onBeforeGetContent.add(function() { + if (t.active) + t._removeWords(); + }); + + ed.onNodeChange.add(function(ed, cm) { + cm.setActive('spellchecker', t.active); + }); + + ed.onSetContent.add(function() { + t._done(); + }); + + ed.onBeforeGetContent.add(function() { + t._done(); + }); + + ed.onBeforeExecCommand.add(function(ed, cmd) { + if (cmd == 'mceFullScreen') + t._done(); + }); + + // Find selected language + t.languages = {}; + each(ed.getParam('spellchecker_languages', '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv', 'hash'), function(v, k) { + if (k.indexOf('+') === 0) { + k = k.substring(1); + t.selectedLang = v; + } + + t.languages[k] = v; + }); + }, + + createControl : function(n, cm) { + var t = this, c, ed = t.editor; + + if (n == 'spellchecker') { + // Use basic button if we use the native spellchecker + if (t.rpcUrl == '{backend}') { + // Create simple toggle button if we have native support + if (t.hasSupport) + c = cm.createButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t}); + + return c; + } + + c = cm.createSplitButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t}); + + c.onRenderMenu.add(function(c, m) { + m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + each(t.languages, function(v, k) { + var o = {icon : 1}, mi; + + o.onclick = function() { + mi.setSelected(1); + t.selectedItem.setSelected(0); + t.selectedItem = mi; + t.selectedLang = v; + }; + + o.title = k; + mi = m.add(o); + mi.setSelected(v == t.selectedLang); + + if (v == t.selectedLang) + t.selectedItem = mi; + }) + }); + + return c; + } + }, + + // Internal functions + + _walk : function(n, f) { + var d = this.editor.getDoc(), w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(this, n); + } else + tinymce.walk(n, f, 'childNodes'); + }, + + _getSeparators : function() { + var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c'); + + // Build word separator regexp + for (i=0; i$1$2'); + v = v.replace(r3, '$1$2'); + + dom.replace(dom.create('span', {'class' : 'mceItemHidden'}, v), n); + } + } + }); + + se.moveToBookmark(b); + }, + + _showMenu : function(ed, e) { + var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin()), wordSpan = e.target; + + e = 0; // Fixes IE memory leak + + if (!m) { + p1 = DOM.getPos(ed.getContentAreaContainer()); + //p2 = DOM.getPos(ed.getContainer()); + + m = ed.controlManager.createDropMenu('spellcheckermenu', { + offset_x : p1.x, + offset_y : p1.y, + 'class' : 'mceNoIcons' + }); + + t._menu = m; + } + + if (dom.hasClass(wordSpan, 'mceItemHiddenSpellWord')) { + m.removeAll(); + m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + + t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(wordSpan.innerHTML)], function(r) { + var ignoreRpc; + + m.removeAll(); + + if (r.length > 0) { + m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + each(r, function(v) { + m.add({title : v, onclick : function() { + dom.replace(ed.getDoc().createTextNode(v), wordSpan); + t._checkDone(); + }}); + }); + + m.addSeparator(); + } else + m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + + ignoreRpc = t.editor.getParam("spellchecker_enable_ignore_rpc", ''); + m.add({ + title : 'spellchecker.ignore_word', + onclick : function() { + var word = wordSpan.innerHTML; + + dom.remove(wordSpan, 1); + t._checkDone(); + + // tell the server if we need to + if (ignoreRpc) { + ed.setProgressState(1); + t._sendRPC('ignoreWord', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + } + }); + + m.add({ + title : 'spellchecker.ignore_words', + onclick : function() { + var word = wordSpan.innerHTML; + + t._removeWords(dom.decode(word)); + t._checkDone(); + + // tell the server if we need to + if (ignoreRpc) { + ed.setProgressState(1); + t._sendRPC('ignoreWords', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + } + }); + + + if (t.editor.getParam("spellchecker_enable_learn_rpc")) { + m.add({ + title : 'spellchecker.learn_word', + onclick : function() { + var word = wordSpan.innerHTML; + + dom.remove(wordSpan, 1); + t._checkDone(); + + ed.setProgressState(1); + t._sendRPC('learnWord', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + }); + } + + m.update(); + }); + + ed.selection.select(wordSpan); + p1 = dom.getPos(wordSpan); + m.showMenu(p1.x, p1.y + wordSpan.offsetHeight - vp.y); + + return tinymce.dom.Event.cancel(e); + } else + m.hideMenu(); + }, + + _checkDone : function() { + var t = this, ed = t.editor, dom = ed.dom, o; + + each(dom.select('span'), function(n) { + if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) { + o = true; + return false; + } + }); + + if (!o) + t._done(); + }, + + _done : function() { + var t = this, la = t.active; + + if (t.active) { + t.active = 0; + t._removeWords(); + + if (t._menu) + t._menu.hideMenu(); + + if (la) + t.editor.nodeChanged(); + } + }, + + _sendRPC : function(m, p, cb) { + var t = this; + + JSONRequest.sendRPC({ + url : t.rpcUrl, + method : m, + params : p, + success : cb, + error : function(e, x) { + t.editor.setProgressState(0); + t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText)); + } + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin); +})(); diff --git a/web/js/globals/tinymce/plugins/spellchecker/img/wline.gif b/web/js/globals/tinymce/plugins/spellchecker/img/wline.gif new file mode 100644 index 0000000000000000000000000000000000000000..7d0a4dbca03cc13177a359a5f175dda819fdf464 GIT binary patch literal 46 ycmZ?wbhEHbWMN=tXkcXcqowu#|9{1wEQ|~cj0`#qKmd|qU}ANVOOs?}um%7FLkRf* literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/plugins/style/css/props.css b/web/js/globals/tinymce/plugins/style/css/props.css new file mode 100644 index 0000000..eb1f264 --- /dev/null +++ b/web/js/globals/tinymce/plugins/style/css/props.css @@ -0,0 +1,13 @@ +#text_font {width:250px;} +#text_size {width:70px;} +.mceAddSelectValue {background:#DDD;} +select, #block_text_indent, #box_width, #box_height, #box_padding_top, #box_padding_right, #box_padding_bottom, #box_padding_left {width:70px;} +#box_margin_top, #box_margin_right, #box_margin_bottom, #box_margin_left, #positioning_width, #positioning_height, #positioning_zindex {width:70px;} +#positioning_placement_top, #positioning_placement_right, #positioning_placement_bottom, #positioning_placement_left {width:70px;} +#positioning_clip_top, #positioning_clip_right, #positioning_clip_bottom, #positioning_clip_left {width:70px;} +.panel_wrapper div.current {padding-top:10px;height:230px;} +.delim {border-left:1px solid gray;} +.tdelim {border-bottom:1px solid gray;} +#block_display {width:145px;} +#list_type {width:115px;} +.disabled {background:#EEE;} diff --git a/web/js/globals/tinymce/plugins/style/editor_plugin.js b/web/js/globals/tinymce/plugins/style/editor_plugin.js new file mode 100644 index 0000000..cab2153 --- /dev/null +++ b/web/js/globals/tinymce/plugins/style/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.StylePlugin",{init:function(a,b){a.addCommand("mceStyleProps",function(){a.windowManager.open({file:b+"/props.htm",width:480+parseInt(a.getLang("style.delta_width",0)),height:320+parseInt(a.getLang("style.delta_height",0)),inline:1},{plugin_url:b,style_text:a.selection.getNode().style.cssText})});a.addCommand("mceSetElementStyle",function(d,c){if(e=a.selection.getNode()){a.dom.setAttrib(e,"style",c);a.execCommand("mceRepaint")}});a.onNodeChange.add(function(d,c,f){c.setDisabled("styleprops",f.nodeName==="BODY")});a.addButton("styleprops",{title:"style.desc",cmd:"mceStyleProps"})},getInfo:function(){return{longname:"Style",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/style",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("style",tinymce.plugins.StylePlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/style/editor_plugin_src.js b/web/js/globals/tinymce/plugins/style/editor_plugin_src.js new file mode 100644 index 0000000..5f7755f --- /dev/null +++ b/web/js/globals/tinymce/plugins/style/editor_plugin_src.js @@ -0,0 +1,55 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.StylePlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceStyleProps', function() { + ed.windowManager.open({ + file : url + '/props.htm', + width : 480 + parseInt(ed.getLang('style.delta_width', 0)), + height : 320 + parseInt(ed.getLang('style.delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + style_text : ed.selection.getNode().style.cssText + }); + }); + + ed.addCommand('mceSetElementStyle', function(ui, v) { + if (e = ed.selection.getNode()) { + ed.dom.setAttrib(e, 'style', v); + ed.execCommand('mceRepaint'); + } + }); + + ed.onNodeChange.add(function(ed, cm, n) { + cm.setDisabled('styleprops', n.nodeName === 'BODY'); + }); + + // Register buttons + ed.addButton('styleprops', {title : 'style.desc', cmd : 'mceStyleProps'}); + }, + + getInfo : function() { + return { + longname : 'Style', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/style', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('style', tinymce.plugins.StylePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/style/js/props.js b/web/js/globals/tinymce/plugins/style/js/props.js new file mode 100644 index 0000000..a8dd93d --- /dev/null +++ b/web/js/globals/tinymce/plugins/style/js/props.js @@ -0,0 +1,641 @@ +tinyMCEPopup.requireLangPack(); + +var defaultFonts = "" + + "Arial, Helvetica, sans-serif=Arial, Helvetica, sans-serif;" + + "Times New Roman, Times, serif=Times New Roman, Times, serif;" + + "Courier New, Courier, mono=Courier New, Courier, mono;" + + "Times New Roman, Times, serif=Times New Roman, Times, serif;" + + "Georgia, Times New Roman, Times, serif=Georgia, Times New Roman, Times, serif;" + + "Verdana, Arial, Helvetica, sans-serif=Verdana, Arial, Helvetica, sans-serif;" + + "Geneva, Arial, Helvetica, sans-serif=Geneva, Arial, Helvetica, sans-serif"; + +var defaultSizes = "9;10;12;14;16;18;24;xx-small;x-small;small;medium;large;x-large;xx-large;smaller;larger"; +var defaultMeasurement = "+pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; +var defaultSpacingMeasurement = "pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;+ems=em;exs=ex;%"; +var defaultIndentMeasurement = "pixels=px;+points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; +var defaultWeight = "normal;bold;bolder;lighter;100;200;300;400;500;600;700;800;900"; +var defaultTextStyle = "normal;italic;oblique"; +var defaultVariant = "normal;small-caps"; +var defaultLineHeight = "normal"; +var defaultAttachment = "fixed;scroll"; +var defaultRepeat = "no-repeat;repeat;repeat-x;repeat-y"; +var defaultPosH = "left;center;right"; +var defaultPosV = "top;center;bottom"; +var defaultVAlign = "baseline;sub;super;top;text-top;middle;bottom;text-bottom"; +var defaultDisplay = "inline;block;list-item;run-in;compact;marker;table;inline-table;table-row-group;table-header-group;table-footer-group;table-row;table-column-group;table-column;table-cell;table-caption;none"; +var defaultBorderStyle = "none;solid;dashed;dotted;double;groove;ridge;inset;outset"; +var defaultBorderWidth = "thin;medium;thick"; +var defaultListType = "disc;circle;square;decimal;lower-roman;upper-roman;lower-alpha;upper-alpha;none"; + +function init() { + var ce = document.getElementById('container'), h; + + ce.style.cssText = tinyMCEPopup.getWindowArg('style_text'); + + h = getBrowserHTML('background_image_browser','background_image','image','advimage'); + document.getElementById("background_image_browser").innerHTML = h; + + document.getElementById('text_color_pickcontainer').innerHTML = getColorPickerHTML('text_color_pick','text_color'); + document.getElementById('background_color_pickcontainer').innerHTML = getColorPickerHTML('background_color_pick','background_color'); + document.getElementById('border_color_top_pickcontainer').innerHTML = getColorPickerHTML('border_color_top_pick','border_color_top'); + document.getElementById('border_color_right_pickcontainer').innerHTML = getColorPickerHTML('border_color_right_pick','border_color_right'); + document.getElementById('border_color_bottom_pickcontainer').innerHTML = getColorPickerHTML('border_color_bottom_pick','border_color_bottom'); + document.getElementById('border_color_left_pickcontainer').innerHTML = getColorPickerHTML('border_color_left_pick','border_color_left'); + + fillSelect(0, 'text_font', 'style_font', defaultFonts, ';', true); + fillSelect(0, 'text_size', 'style_font_size', defaultSizes, ';', true); + fillSelect(0, 'text_size_measurement', 'style_font_size_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'text_case', 'style_text_case', "capitalize;uppercase;lowercase", ';', true); + fillSelect(0, 'text_weight', 'style_font_weight', defaultWeight, ';', true); + fillSelect(0, 'text_style', 'style_font_style', defaultTextStyle, ';', true); + fillSelect(0, 'text_variant', 'style_font_variant', defaultVariant, ';', true); + fillSelect(0, 'text_lineheight', 'style_font_line_height', defaultLineHeight, ';', true); + fillSelect(0, 'text_lineheight_measurement', 'style_font_line_height_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'background_attachment', 'style_background_attachment', defaultAttachment, ';', true); + fillSelect(0, 'background_repeat', 'style_background_repeat', defaultRepeat, ';', true); + + fillSelect(0, 'background_hpos_measurement', 'style_background_hpos_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'background_vpos_measurement', 'style_background_vpos_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'background_hpos', 'style_background_hpos', defaultPosH, ';', true); + fillSelect(0, 'background_vpos', 'style_background_vpos', defaultPosV, ';', true); + + fillSelect(0, 'block_wordspacing', 'style_wordspacing', 'normal', ';', true); + fillSelect(0, 'block_wordspacing_measurement', 'style_wordspacing_measurement', defaultSpacingMeasurement, ';', true); + fillSelect(0, 'block_letterspacing', 'style_letterspacing', 'normal', ';', true); + fillSelect(0, 'block_letterspacing_measurement', 'style_letterspacing_measurement', defaultSpacingMeasurement, ';', true); + fillSelect(0, 'block_vertical_alignment', 'style_vertical_alignment', defaultVAlign, ';', true); + fillSelect(0, 'block_text_align', 'style_text_align', "left;right;center;justify", ';', true); + fillSelect(0, 'block_whitespace', 'style_whitespace', "normal;pre;nowrap", ';', true); + fillSelect(0, 'block_display', 'style_display', defaultDisplay, ';', true); + fillSelect(0, 'block_text_indent_measurement', 'style_text_indent_measurement', defaultIndentMeasurement, ';', true); + + fillSelect(0, 'box_width_measurement', 'style_box_width_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_height_measurement', 'style_box_height_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_float', 'style_float', 'left;right;none', ';', true); + fillSelect(0, 'box_clear', 'style_clear', 'left;right;both;none', ';', true); + fillSelect(0, 'box_padding_left_measurement', 'style_padding_left_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_top_measurement', 'style_padding_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_bottom_measurement', 'style_padding_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_right_measurement', 'style_padding_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_left_measurement', 'style_margin_left_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_top_measurement', 'style_margin_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_bottom_measurement', 'style_margin_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_right_measurement', 'style_margin_right_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'border_style_top', 'style_border_style_top', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_right', 'style_border_style_right', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_bottom', 'style_border_style_bottom', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_left', 'style_border_style_left', defaultBorderStyle, ';', true); + + fillSelect(0, 'border_width_top', 'style_border_width_top', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_right', 'style_border_width_right', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_bottom', 'style_border_width_bottom', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_left', 'style_border_width_left', defaultBorderWidth, ';', true); + + fillSelect(0, 'border_width_top_measurement', 'style_border_width_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_right_measurement', 'style_border_width_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_bottom_measurement', 'style_border_width_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_left_measurement', 'style_border_width_left_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'list_type', 'style_list_type', defaultListType, ';', true); + fillSelect(0, 'list_position', 'style_list_position', "inside;outside", ';', true); + + fillSelect(0, 'positioning_type', 'style_positioning_type', "absolute;relative;static", ';', true); + fillSelect(0, 'positioning_visibility', 'style_positioning_visibility', "inherit;visible;hidden", ';', true); + + fillSelect(0, 'positioning_width_measurement', 'style_positioning_width_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_height_measurement', 'style_positioning_height_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_overflow', 'style_positioning_overflow', "visible;hidden;scroll;auto", ';', true); + + fillSelect(0, 'positioning_placement_top_measurement', 'style_positioning_placement_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_right_measurement', 'style_positioning_placement_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_bottom_measurement', 'style_positioning_placement_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_left_measurement', 'style_positioning_placement_left_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'positioning_clip_top_measurement', 'style_positioning_clip_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_right_measurement', 'style_positioning_clip_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_bottom_measurement', 'style_positioning_clip_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_left_measurement', 'style_positioning_clip_left_measurement', defaultMeasurement, ';', true); + + TinyMCE_EditableSelects.init(); + setupFormData(); + showDisabledControls(); +} + +function setupFormData() { + var ce = document.getElementById('container'), f = document.forms[0], s, b, i; + + // Setup text fields + + selectByValue(f, 'text_font', ce.style.fontFamily, true, true); + selectByValue(f, 'text_size', getNum(ce.style.fontSize), true, true); + selectByValue(f, 'text_size_measurement', getMeasurement(ce.style.fontSize)); + selectByValue(f, 'text_weight', ce.style.fontWeight, true, true); + selectByValue(f, 'text_style', ce.style.fontStyle, true, true); + selectByValue(f, 'text_lineheight', getNum(ce.style.lineHeight), true, true); + selectByValue(f, 'text_lineheight_measurement', getMeasurement(ce.style.lineHeight)); + selectByValue(f, 'text_case', ce.style.textTransform, true, true); + selectByValue(f, 'text_variant', ce.style.fontVariant, true, true); + f.text_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.color); + updateColor('text_color_pick', 'text_color'); + f.text_underline.checked = inStr(ce.style.textDecoration, 'underline'); + f.text_overline.checked = inStr(ce.style.textDecoration, 'overline'); + f.text_linethrough.checked = inStr(ce.style.textDecoration, 'line-through'); + f.text_blink.checked = inStr(ce.style.textDecoration, 'blink'); + + // Setup background fields + + f.background_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.backgroundColor); + updateColor('background_color_pick', 'background_color'); + f.background_image.value = ce.style.backgroundImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); + selectByValue(f, 'background_repeat', ce.style.backgroundRepeat, true, true); + selectByValue(f, 'background_attachment', ce.style.backgroundAttachment, true, true); + selectByValue(f, 'background_hpos', getNum(getVal(ce.style.backgroundPosition, 0)), true, true); + selectByValue(f, 'background_hpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 0))); + selectByValue(f, 'background_vpos', getNum(getVal(ce.style.backgroundPosition, 1)), true, true); + selectByValue(f, 'background_vpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 1))); + + // Setup block fields + + selectByValue(f, 'block_wordspacing', getNum(ce.style.wordSpacing), true, true); + selectByValue(f, 'block_wordspacing_measurement', getMeasurement(ce.style.wordSpacing)); + selectByValue(f, 'block_letterspacing', getNum(ce.style.letterSpacing), true, true); + selectByValue(f, 'block_letterspacing_measurement', getMeasurement(ce.style.letterSpacing)); + selectByValue(f, 'block_vertical_alignment', ce.style.verticalAlign, true, true); + selectByValue(f, 'block_text_align', ce.style.textAlign, true, true); + f.block_text_indent.value = getNum(ce.style.textIndent); + selectByValue(f, 'block_text_indent_measurement', getMeasurement(ce.style.textIndent)); + selectByValue(f, 'block_whitespace', ce.style.whiteSpace, true, true); + selectByValue(f, 'block_display', ce.style.display, true, true); + + // Setup box fields + + f.box_width.value = getNum(ce.style.width); + selectByValue(f, 'box_width_measurement', getMeasurement(ce.style.width)); + + f.box_height.value = getNum(ce.style.height); + selectByValue(f, 'box_height_measurement', getMeasurement(ce.style.height)); + + if (tinymce.isGecko) + selectByValue(f, 'box_float', ce.style.cssFloat, true, true); + else + selectByValue(f, 'box_float', ce.style.styleFloat, true, true); + + selectByValue(f, 'box_clear', ce.style.clear, true, true); + + setupBox(f, ce, 'box_padding', 'padding', ''); + setupBox(f, ce, 'box_margin', 'margin', ''); + + // Setup border fields + + setupBox(f, ce, 'border_style', 'border', 'Style'); + setupBox(f, ce, 'border_width', 'border', 'Width'); + setupBox(f, ce, 'border_color', 'border', 'Color'); + + updateColor('border_color_top_pick', 'border_color_top'); + updateColor('border_color_right_pick', 'border_color_right'); + updateColor('border_color_bottom_pick', 'border_color_bottom'); + updateColor('border_color_left_pick', 'border_color_left'); + + f.elements.border_color_top.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_top.value); + f.elements.border_color_right.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_right.value); + f.elements.border_color_bottom.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_bottom.value); + f.elements.border_color_left.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_left.value); + + // Setup list fields + + selectByValue(f, 'list_type', ce.style.listStyleType, true, true); + selectByValue(f, 'list_position', ce.style.listStylePosition, true, true); + f.list_bullet_image.value = ce.style.listStyleImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); + + // Setup box fields + + selectByValue(f, 'positioning_type', ce.style.position, true, true); + selectByValue(f, 'positioning_visibility', ce.style.visibility, true, true); + selectByValue(f, 'positioning_overflow', ce.style.overflow, true, true); + f.positioning_zindex.value = ce.style.zIndex ? ce.style.zIndex : ""; + + f.positioning_width.value = getNum(ce.style.width); + selectByValue(f, 'positioning_width_measurement', getMeasurement(ce.style.width)); + + f.positioning_height.value = getNum(ce.style.height); + selectByValue(f, 'positioning_height_measurement', getMeasurement(ce.style.height)); + + setupBox(f, ce, 'positioning_placement', '', '', ['top', 'right', 'bottom', 'left']); + + s = ce.style.clip.replace(new RegExp("rect\\('?([^']*)'?\\)", 'gi'), "$1"); + s = s.replace(/,/g, ' '); + + if (!hasEqualValues([getVal(s, 0), getVal(s, 1), getVal(s, 2), getVal(s, 3)])) { + f.positioning_clip_top.value = getNum(getVal(s, 0)); + selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); + f.positioning_clip_right.value = getNum(getVal(s, 1)); + selectByValue(f, 'positioning_clip_right_measurement', getMeasurement(getVal(s, 1))); + f.positioning_clip_bottom.value = getNum(getVal(s, 2)); + selectByValue(f, 'positioning_clip_bottom_measurement', getMeasurement(getVal(s, 2))); + f.positioning_clip_left.value = getNum(getVal(s, 3)); + selectByValue(f, 'positioning_clip_left_measurement', getMeasurement(getVal(s, 3))); + } else { + f.positioning_clip_top.value = getNum(getVal(s, 0)); + selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); + f.positioning_clip_right.value = f.positioning_clip_bottom.value = f.positioning_clip_left.value; + } + +// setupBox(f, ce, '', 'border', 'Color'); +} + +function getMeasurement(s) { + return s.replace(/^([0-9.]+)(.*)$/, "$2"); +} + +function getNum(s) { + if (new RegExp('^(?:[0-9.]+)(?:[a-z%]+)$', 'gi').test(s)) + return s.replace(/[^0-9.]/g, ''); + + return s; +} + +function inStr(s, n) { + return new RegExp(n, 'gi').test(s); +} + +function getVal(s, i) { + var a = s.split(' '); + + if (a.length > 1) + return a[i]; + + return ""; +} + +function setValue(f, n, v) { + if (f.elements[n].type == "text") + f.elements[n].value = v; + else + selectByValue(f, n, v, true, true); +} + +function setupBox(f, ce, fp, pr, sf, b) { + if (typeof(b) == "undefined") + b = ['Top', 'Right', 'Bottom', 'Left']; + + if (isSame(ce, pr, sf, b)) { + f.elements[fp + "_same"].checked = true; + + setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); + f.elements[fp + "_top"].disabled = false; + + f.elements[fp + "_right"].value = ""; + f.elements[fp + "_right"].disabled = true; + f.elements[fp + "_bottom"].value = ""; + f.elements[fp + "_bottom"].disabled = true; + f.elements[fp + "_left"].value = ""; + f.elements[fp + "_left"].disabled = true; + + if (f.elements[fp + "_top_measurement"]) { + selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); + f.elements[fp + "_left_measurement"].disabled = true; + f.elements[fp + "_bottom_measurement"].disabled = true; + f.elements[fp + "_right_measurement"].disabled = true; + } + } else { + f.elements[fp + "_same"].checked = false; + + setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); + f.elements[fp + "_top"].disabled = false; + + setValue(f, fp + "_right", getNum(ce.style[pr + b[1] + sf])); + f.elements[fp + "_right"].disabled = false; + + setValue(f, fp + "_bottom", getNum(ce.style[pr + b[2] + sf])); + f.elements[fp + "_bottom"].disabled = false; + + setValue(f, fp + "_left", getNum(ce.style[pr + b[3] + sf])); + f.elements[fp + "_left"].disabled = false; + + if (f.elements[fp + "_top_measurement"]) { + selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); + selectByValue(f, fp + '_right_measurement', getMeasurement(ce.style[pr + b[1] + sf])); + selectByValue(f, fp + '_bottom_measurement', getMeasurement(ce.style[pr + b[2] + sf])); + selectByValue(f, fp + '_left_measurement', getMeasurement(ce.style[pr + b[3] + sf])); + f.elements[fp + "_left_measurement"].disabled = false; + f.elements[fp + "_bottom_measurement"].disabled = false; + f.elements[fp + "_right_measurement"].disabled = false; + } + } +} + +function isSame(e, pr, sf, b) { + var a = [], i, x; + + if (typeof(b) == "undefined") + b = ['Top', 'Right', 'Bottom', 'Left']; + + if (typeof(sf) == "undefined" || sf == null) + sf = ""; + + a[0] = e.style[pr + b[0] + sf]; + a[1] = e.style[pr + b[1] + sf]; + a[2] = e.style[pr + b[2] + sf]; + a[3] = e.style[pr + b[3] + sf]; + + for (i=0; i 0 ? s.substring(1) : s; + + if (f.text_none.checked) + s = "none"; + + ce.style.textDecoration = s; + + // Build background styles + + ce.style.backgroundColor = f.background_color.value; + ce.style.backgroundImage = f.background_image.value != "" ? "url(" + f.background_image.value + ")" : ""; + ce.style.backgroundRepeat = f.background_repeat.value; + ce.style.backgroundAttachment = f.background_attachment.value; + + if (f.background_hpos.value != "") { + s = ""; + s += f.background_hpos.value + (isNum(f.background_hpos.value) ? f.background_hpos_measurement.value : "") + " "; + s += f.background_vpos.value + (isNum(f.background_vpos.value) ? f.background_vpos_measurement.value : ""); + ce.style.backgroundPosition = s; + } + + // Build block styles + + ce.style.wordSpacing = f.block_wordspacing.value + (isNum(f.block_wordspacing.value) ? f.block_wordspacing_measurement.value : ""); + ce.style.letterSpacing = f.block_letterspacing.value + (isNum(f.block_letterspacing.value) ? f.block_letterspacing_measurement.value : ""); + ce.style.verticalAlign = f.block_vertical_alignment.value; + ce.style.textAlign = f.block_text_align.value; + ce.style.textIndent = f.block_text_indent.value + (isNum(f.block_text_indent.value) ? f.block_text_indent_measurement.value : ""); + ce.style.whiteSpace = f.block_whitespace.value; + ce.style.display = f.block_display.value; + + // Build box styles + + ce.style.width = f.box_width.value + (isNum(f.box_width.value) ? f.box_width_measurement.value : ""); + ce.style.height = f.box_height.value + (isNum(f.box_height.value) ? f.box_height_measurement.value : ""); + ce.style.styleFloat = f.box_float.value; + + if (tinymce.isGecko) + ce.style.cssFloat = f.box_float.value; + + ce.style.clear = f.box_clear.value; + + if (!f.box_padding_same.checked) { + ce.style.paddingTop = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); + ce.style.paddingRight = f.box_padding_right.value + (isNum(f.box_padding_right.value) ? f.box_padding_right_measurement.value : ""); + ce.style.paddingBottom = f.box_padding_bottom.value + (isNum(f.box_padding_bottom.value) ? f.box_padding_bottom_measurement.value : ""); + ce.style.paddingLeft = f.box_padding_left.value + (isNum(f.box_padding_left.value) ? f.box_padding_left_measurement.value : ""); + } else + ce.style.padding = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); + + if (!f.box_margin_same.checked) { + ce.style.marginTop = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); + ce.style.marginRight = f.box_margin_right.value + (isNum(f.box_margin_right.value) ? f.box_margin_right_measurement.value : ""); + ce.style.marginBottom = f.box_margin_bottom.value + (isNum(f.box_margin_bottom.value) ? f.box_margin_bottom_measurement.value : ""); + ce.style.marginLeft = f.box_margin_left.value + (isNum(f.box_margin_left.value) ? f.box_margin_left_measurement.value : ""); + } else + ce.style.margin = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); + + // Build border styles + + if (!f.border_style_same.checked) { + ce.style.borderTopStyle = f.border_style_top.value; + ce.style.borderRightStyle = f.border_style_right.value; + ce.style.borderBottomStyle = f.border_style_bottom.value; + ce.style.borderLeftStyle = f.border_style_left.value; + } else + ce.style.borderStyle = f.border_style_top.value; + + if (!f.border_width_same.checked) { + ce.style.borderTopWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); + ce.style.borderRightWidth = f.border_width_right.value + (isNum(f.border_width_right.value) ? f.border_width_right_measurement.value : ""); + ce.style.borderBottomWidth = f.border_width_bottom.value + (isNum(f.border_width_bottom.value) ? f.border_width_bottom_measurement.value : ""); + ce.style.borderLeftWidth = f.border_width_left.value + (isNum(f.border_width_left.value) ? f.border_width_left_measurement.value : ""); + } else + ce.style.borderWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); + + if (!f.border_color_same.checked) { + ce.style.borderTopColor = f.border_color_top.value; + ce.style.borderRightColor = f.border_color_right.value; + ce.style.borderBottomColor = f.border_color_bottom.value; + ce.style.borderLeftColor = f.border_color_left.value; + } else + ce.style.borderColor = f.border_color_top.value; + + // Build list styles + + ce.style.listStyleType = f.list_type.value; + ce.style.listStylePosition = f.list_position.value; + ce.style.listStyleImage = f.list_bullet_image.value != "" ? "url(" + f.list_bullet_image.value + ")" : ""; + + // Build positioning styles + + ce.style.position = f.positioning_type.value; + ce.style.visibility = f.positioning_visibility.value; + + if (ce.style.width == "") + ce.style.width = f.positioning_width.value + (isNum(f.positioning_width.value) ? f.positioning_width_measurement.value : ""); + + if (ce.style.height == "") + ce.style.height = f.positioning_height.value + (isNum(f.positioning_height.value) ? f.positioning_height_measurement.value : ""); + + ce.style.zIndex = f.positioning_zindex.value; + ce.style.overflow = f.positioning_overflow.value; + + if (!f.positioning_placement_same.checked) { + ce.style.top = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); + ce.style.right = f.positioning_placement_right.value + (isNum(f.positioning_placement_right.value) ? f.positioning_placement_right_measurement.value : ""); + ce.style.bottom = f.positioning_placement_bottom.value + (isNum(f.positioning_placement_bottom.value) ? f.positioning_placement_bottom_measurement.value : ""); + ce.style.left = f.positioning_placement_left.value + (isNum(f.positioning_placement_left.value) ? f.positioning_placement_left_measurement.value : ""); + } else { + s = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); + ce.style.top = s; + ce.style.right = s; + ce.style.bottom = s; + ce.style.left = s; + } + + if (!f.positioning_clip_same.checked) { + s = "rect("; + s += (isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_right.value) ? f.positioning_clip_right.value + f.positioning_clip_right_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_bottom.value) ? f.positioning_clip_bottom.value + f.positioning_clip_bottom_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_left.value) ? f.positioning_clip_left.value + f.positioning_clip_left_measurement.value : "auto"); + s += ")"; + + if (s != "rect(auto auto auto auto)") + ce.style.clip = s; + } else { + s = "rect("; + t = isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto"; + s += t + " "; + s += t + " "; + s += t + " "; + s += t + ")"; + + if (s != "rect(auto auto auto auto)") + ce.style.clip = s; + } + + ce.style.cssText = ce.style.cssText; +} + +function isNum(s) { + return new RegExp('[0-9]+', 'g').test(s); +} + +function showDisabledControls() { + var f = document.forms, i, a; + + for (i=0; i 1) { + addSelectValue(f, s, p[0], p[1]); + + if (se) + selectByValue(f, s, p[1]); + } else { + addSelectValue(f, s, p[0], p[0]); + + if (se) + selectByValue(f, s, p[0]); + } + } +} + +function toggleSame(ce, pre) { + var el = document.forms[0].elements, i; + + if (ce.checked) { + el[pre + "_top"].disabled = false; + el[pre + "_right"].disabled = true; + el[pre + "_bottom"].disabled = true; + el[pre + "_left"].disabled = true; + + if (el[pre + "_top_measurement"]) { + el[pre + "_top_measurement"].disabled = false; + el[pre + "_right_measurement"].disabled = true; + el[pre + "_bottom_measurement"].disabled = true; + el[pre + "_left_measurement"].disabled = true; + } + } else { + el[pre + "_top"].disabled = false; + el[pre + "_right"].disabled = false; + el[pre + "_bottom"].disabled = false; + el[pre + "_left"].disabled = false; + + if (el[pre + "_top_measurement"]) { + el[pre + "_top_measurement"].disabled = false; + el[pre + "_right_measurement"].disabled = false; + el[pre + "_bottom_measurement"].disabled = false; + el[pre + "_left_measurement"].disabled = false; + } + } + + showDisabledControls(); +} + +function synch(fr, to) { + var f = document.forms[0]; + + f.elements[to].value = f.elements[fr].value; + + if (f.elements[fr + "_measurement"]) + selectByValue(f, to + "_measurement", f.elements[fr + "_measurement"].value); +} + +tinyMCEPopup.onInit.add(init); diff --git a/web/js/globals/tinymce/plugins/style/langs/en_dlg.js b/web/js/globals/tinymce/plugins/style/langs/en_dlg.js new file mode 100644 index 0000000..5026313 --- /dev/null +++ b/web/js/globals/tinymce/plugins/style/langs/en_dlg.js @@ -0,0 +1,63 @@ +tinyMCE.addI18n('en.style_dlg',{ +title:"Edit CSS Style", +apply:"Apply", +text_tab:"Text", +background_tab:"Background", +block_tab:"Block", +box_tab:"Box", +border_tab:"Border", +list_tab:"List", +positioning_tab:"Positioning", +text_props:"Text", +text_font:"Font", +text_size:"Size", +text_weight:"Weight", +text_style:"Style", +text_variant:"Variant", +text_lineheight:"Line height", +text_case:"Case", +text_color:"Color", +text_decoration:"Decoration", +text_overline:"overline", +text_underline:"underline", +text_striketrough:"strikethrough", +text_blink:"blink", +text_none:"none", +background_color:"Background color", +background_image:"Background image", +background_repeat:"Repeat", +background_attachment:"Attachment", +background_hpos:"Horizontal position", +background_vpos:"Vertical position", +block_wordspacing:"Word spacing", +block_letterspacing:"Letter spacing", +block_vertical_alignment:"Vertical alignment", +block_text_align:"Text align", +block_text_indent:"Text indent", +block_whitespace:"Whitespace", +block_display:"Display", +box_width:"Width", +box_height:"Height", +box_float:"Float", +box_clear:"Clear", +padding:"Padding", +same:"Same for all", +top:"Top", +right:"Right", +bottom:"Bottom", +left:"Left", +margin:"Margin", +style:"Style", +width:"Width", +height:"Height", +color:"Color", +list_type:"Type", +bullet_image:"Bullet image", +position:"Position", +positioning_type:"Type", +visibility:"Visibility", +zindex:"Z-index", +overflow:"Overflow", +placement:"Placement", +clip:"Clip" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/style/props.htm b/web/js/globals/tinymce/plugins/style/props.htm new file mode 100644 index 0000000..549ed04 --- /dev/null +++ b/web/js/globals/tinymce/plugins/style/props.htm @@ -0,0 +1,723 @@ + + + + {#style_dlg.title} + + + + + + + + + +
      + + +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + + +
       
      +
      + +
      + + + +
      + + + + + + +
      + +  
      +
      + +
      + + + + + +
       
      +
      {#style_dlg.text_decoration} + + + + + + + + + + + + + + + + + + + + + +
      +
      +
      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + +
       
      +
      + + + + +
       
      +
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      +
      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      +
      + +
      + + + + + + + + + + + + + + +
      + + + + + + +
       
      +
         
      + + + + + + +
       
      +
         
      +
      +
      + {#style_dlg.padding} + + + + + + + + + + + + + + + + + + + + + + +
       
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      +
      +
      + +
      +
      + {#style_dlg.margin} + + + + + + + + + + + + + + + + + + + + + + +
       
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      + + + + + + +
       
      +
      +
      +
      +
      +
      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        {#style_dlg.style} {#style_dlg.width} {#style_dlg.color}
            
      {#style_dlg.top}   + + + + + + +
       
      +
        + + + + + +
       
      +
      {#style_dlg.right}   + + + + + + +
       
      +
        + + + + + +
       
      +
      {#style_dlg.bottom}   + + + + + + +
       
      +
        + + + + + +
       
      +
      {#style_dlg.left}   + + + + + + +
       
      +
        + + + + + +
       
      +
      +
      + +
      + + + + + + + + + + + + + + + +
      +
      + +
      + + + + + + + + + + + + + + + + + + + + + +
         
      + + + + + + +
       
      +
         
      + + + + + + +
       
      +
         
      + +
      +
      + {#style_dlg.placement} + + + + + + + + + + + + + + + + + + + + + + +
       
      {#style_dlg.top} + + + + + + +
       
      +
      {#style_dlg.right} + + + + + + +
       
      +
      {#style_dlg.bottom} + + + + + + +
       
      +
      {#style_dlg.left} + + + + + + +
       
      +
      +
      +
      + +
      +
      + {#style_dlg.clip} + + + + + + + + + + + + + + + + + + + + + + +
       
      {#style_dlg.top} + + + + + + +
       
      +
      {#style_dlg.right} + + + + + + +
       
      +
      {#style_dlg.bottom} + + + + + + +
       
      +
      {#style_dlg.left} + + + + + + +
       
      +
      +
      +
      +
      +
      +
      + +
      + + + +
      +
      + +
      +
      +
      + + + diff --git a/web/js/globals/tinymce/plugins/tabfocus/editor_plugin.js b/web/js/globals/tinymce/plugins/tabfocus/editor_plugin.js new file mode 100644 index 0000000..27d2440 --- /dev/null +++ b/web/js/globals/tinymce/plugins/tabfocus/editor_plugin.js @@ -0,0 +1 @@ +(function(){var c=tinymce.DOM,a=tinymce.dom.Event,d=tinymce.each,b=tinymce.explode;tinymce.create("tinymce.plugins.TabFocusPlugin",{init:function(f,g){function e(i,j){if(j.keyCode===9){return a.cancel(j)}}function h(l,p){var j,m,o,n,k;function q(i){o=c.getParent(l.id,"form");n=o.elements;if(o){d(n,function(s,r){if(s.id==l.id){j=r;return false}});if(i>0){for(m=j+1;m=0;m--){if(n[m].type!="hidden"){return n[m]}}}}return null}if(p.keyCode===9){k=b(l.getParam("tab_focus",l.getParam("tabfocus_elements",":prev,:next")));if(k.length==1){k[1]=k[0];k[0]=":prev"}if(p.shiftKey){if(k[0]==":prev"){n=q(-1)}else{n=c.get(k[0])}}else{if(k[1]==":next"){n=q(1)}else{n=c.get(k[1])}}if(n){if(l=tinymce.get(n.id||n.name)){l.focus()}else{window.setTimeout(function(){window.focus();n.focus()},10)}return a.cancel(p)}}}f.onKeyUp.add(e);if(tinymce.isGecko){f.onKeyPress.add(h);f.onKeyDown.add(e)}else{f.onKeyDown.add(h)}f.onInit.add(function(){d(c.select("a:first,a:last",f.getContainer()),function(i){a.add(i,"focus",function(){f.focus()})})})},getInfo:function(){return{longname:"Tabfocus",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("tabfocus",tinymce.plugins.TabFocusPlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/tabfocus/editor_plugin_src.js b/web/js/globals/tinymce/plugins/tabfocus/editor_plugin_src.js new file mode 100644 index 0000000..c2be2f4 --- /dev/null +++ b/web/js/globals/tinymce/plugins/tabfocus/editor_plugin_src.js @@ -0,0 +1,112 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, explode = tinymce.explode; + + tinymce.create('tinymce.plugins.TabFocusPlugin', { + init : function(ed, url) { + function tabCancel(ed, e) { + if (e.keyCode === 9) + return Event.cancel(e); + }; + + function tabHandler(ed, e) { + var x, i, f, el, v; + + function find(d) { + f = DOM.getParent(ed.id, 'form'); + el = f.elements; + + if (f) { + each(el, function(e, i) { + if (e.id == ed.id) { + x = i; + return false; + } + }); + + if (d > 0) { + for (i = x + 1; i < el.length; i++) { + if (el[i].type != 'hidden') + return el[i]; + } + } else { + for (i = x - 1; i >= 0; i--) { + if (el[i].type != 'hidden') + return el[i]; + } + } + } + + return null; + }; + + if (e.keyCode === 9) { + v = explode(ed.getParam('tab_focus', ed.getParam('tabfocus_elements', ':prev,:next'))); + + if (v.length == 1) { + v[1] = v[0]; + v[0] = ':prev'; + } + + // Find element to focus + if (e.shiftKey) { + if (v[0] == ':prev') + el = find(-1); + else + el = DOM.get(v[0]); + } else { + if (v[1] == ':next') + el = find(1); + else + el = DOM.get(v[1]); + } + + if (el) { + if (ed = tinymce.get(el.id || el.name)) + ed.focus(); + else + window.setTimeout(function() {window.focus();el.focus();}, 10); + + return Event.cancel(e); + } + } + }; + + ed.onKeyUp.add(tabCancel); + + if (tinymce.isGecko) { + ed.onKeyPress.add(tabHandler); + ed.onKeyDown.add(tabCancel); + } else + ed.onKeyDown.add(tabHandler); + + ed.onInit.add(function() { + each(DOM.select('a:first,a:last', ed.getContainer()), function(n) { + Event.add(n, 'focus', function() {ed.focus();}); + }); + }); + }, + + getInfo : function() { + return { + longname : 'Tabfocus', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('tabfocus', tinymce.plugins.TabFocusPlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/table/cell.htm b/web/js/globals/tinymce/plugins/table/cell.htm new file mode 100644 index 0000000..d243e1d --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/cell.htm @@ -0,0 +1,178 @@ + + + + {#table_dlg.cell_title} + + + + + + + + +
      + + +
      +
      +
      + {#table_dlg.general_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + +
      + + + +
      + +
      +
      +
      + +
      +
      + {#table_dlg.advanced_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + +
      + + + + + +
       
      +
      + + + + + +
       
      +
      + + + + + +
       
      +
      +
      +
      +
      + +
      +
      + +
      + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/table/css/cell.css b/web/js/globals/tinymce/plugins/table/css/cell.css new file mode 100644 index 0000000..a067ecd --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/css/cell.css @@ -0,0 +1,17 @@ +/* CSS file for cell dialog in the table plugin */ + +.panel_wrapper div.current { + height: 200px; +} + +.advfield { + width: 200px; +} + +#action { + margin-bottom: 3px; +} + +#class { + width: 150px; +} \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/table/css/row.css b/web/js/globals/tinymce/plugins/table/css/row.css new file mode 100644 index 0000000..1f7755d --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/css/row.css @@ -0,0 +1,25 @@ +/* CSS file for row dialog in the table plugin */ + +.panel_wrapper div.current { + height: 200px; +} + +.advfield { + width: 200px; +} + +#action { + margin-bottom: 3px; +} + +#rowtype,#align,#valign,#class,#height { + width: 150px; +} + +#height { + width: 50px; +} + +.col2 { + padding-left: 20px; +} diff --git a/web/js/globals/tinymce/plugins/table/css/table.css b/web/js/globals/tinymce/plugins/table/css/table.css new file mode 100644 index 0000000..d11c3f6 --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/css/table.css @@ -0,0 +1,13 @@ +/* CSS file for table dialog in the table plugin */ + +.panel_wrapper div.current { + height: 245px; +} + +.advfield { + width: 200px; +} + +#class { + width: 150px; +} diff --git a/web/js/globals/tinymce/plugins/table/editor_plugin.js b/web/js/globals/tinymce/plugins/table/editor_plugin.js new file mode 100644 index 0000000..484f81c --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/editor_plugin.js @@ -0,0 +1 @@ +(function(c){var d=c.each;function b(f,g){var h=g.ownerDocument,e=h.createRange(),j;e.setStartBefore(g);e.setEnd(f.endContainer,f.endOffset);j=h.createElement("body");j.appendChild(e.cloneContents());return j.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length==0}function a(G,F,J){var f,K,C,o;s();o=F.getParent(J.getStart(),"th,td");if(o){K=E(o);C=H();o=w(K.x,K.y)}function z(M,L){M=M.cloneNode(L);M.removeAttribute("id");return M}function s(){var L=0;f=[];d(["thead","tbody","tfoot"],function(M){var N=F.select("> "+M+" tr",G);d(N,function(O,P){P+=L;d(F.select("> td, > th",O),function(V,Q){var R,S,T,U;if(f[P]){while(f[P][Q]){Q++}}T=h(V,"rowspan");U=h(V,"colspan");for(S=P;S'}return false}},"childNodes");L=z(L,false);L.rowSpan=L.colSpan=1;if(M){L.appendChild(M)}else{if(!c.isIE){L.innerHTML='
      '}}return L}function q(){var L=F.createRng();d(F.select("tr",G),function(M){if(M.cells.length==0){F.remove(M)}});if(F.select("tr",G).length==0){L.setStartAfter(G);L.setEndAfter(G);J.setRng(L);F.remove(G);return}d(F.select("thead,tbody,tfoot",G),function(M){if(M.rows.length==0){F.remove(M)}});s();row=f[Math.min(f.length-1,K.y)];if(row){J.select(row[Math.min(row.length-1,K.x)].elm,true);J.collapse(true)}}function t(R,P,T,Q){var O,M,L,N,S;O=f[P][R].elm.parentNode;for(L=1;L<=T;L++){O=F.getNext(O,"tr");if(O){for(M=R;M>=0;M--){S=f[P+L][M].elm;if(S.parentNode==O){for(N=1;N<=Q;N++){F.insertAfter(e(S),S)}break}}if(M==-1){for(N=1;N<=Q;N++){O.insertBefore(e(O.cells[0]),O.cells[0])}}}}}function B(){d(f,function(L,M){d(L,function(O,N){var R,Q,S,P;if(j(O)){O=O.elm;R=h(O,"colspan");Q=h(O,"rowspan");if(R>1||Q>1){O.colSpan=O.rowSpan=1;for(P=0;P1){Q.rowSpan=rowSpan+1;continue}}else{if(L>0&&f[L-1][P]){T=f[L-1][P].elm;rowSpan=h(T,"rowspan");if(rowSpan>1){T.rowSpan=rowSpan+1;continue}}}M=e(Q);M.colSpan=Q.colSpan;S.appendChild(M);N=Q}}if(S.hasChildNodes()){if(!O){F.insertAfter(S,R)}else{R.parentNode.insertBefore(S,R)}}}function g(M){var N,L;d(f,function(O,P){d(O,function(R,Q){if(j(R)){N=Q;if(M){return false}}});if(M){return !N}});d(f,function(R,S){var O=R[N].elm,P,Q;if(O!=L){Q=h(O,"colspan");P=h(O,"rowspan");if(Q==1){if(!M){F.insertAfter(e(O),O);t(N,S,P-1,Q)}else{O.parentNode.insertBefore(e(O),O);t(N,S,P-1,Q)}}else{O.colSpan++}L=O}})}function n(){var L=[];d(f,function(M,N){d(M,function(P,O){if(j(P)&&c.inArray(L,O)===-1){d(f,function(S){var Q=S[O].elm,R;R=h(Q,"colspan");if(R>1){Q.colSpan=R-1}else{F.remove(Q)}});L.push(O)}})});q()}function m(){var M;function L(P){var O,Q,N;O=F.getNext(P,"tr");d(P.cells,function(R){var S=h(R,"rowspan");if(S>1){R.rowSpan=S-1;Q=E(R);t(Q.x,Q.y,1,1)}});Q=E(P.cells[0]);d(f[Q.y],function(R){var S;R=R.elm;if(R!=N){S=h(R,"rowspan");if(S<=1){F.remove(R)}else{R.rowSpan=S-1}N=R}})}M=k();d(M.reverse(),function(N){L(N)});q()}function D(){var L=k();F.remove(L);q();return L}function I(){var L=k();d(L,function(N,M){L[M]=z(N,true)});return L}function A(N,M){var O=k(),L=O[M?0:O.length-1],P=L.cells.length;d(f,function(R){var Q;P=0;d(R,function(T,S){if(T.real){P+=T.colspan}if(T.elm.parentNode==L){Q=1}});if(Q){return false}});if(!M){N.reverse()}d(N,function(S){var R=S.cells.length,Q;for(i=0;iM){M=Q}if(P>L){L=P}if(R.real){T=R.colspan-1;S=R.rowspan-1;if(T){if(Q+T>M){M=Q+T}}if(S){if(P+S>L){L=P+S}}}}})});return{x:M,y:L}}function u(R){var O,N,T,S,M,L,P,Q;C=E(R);if(K&&C){O=Math.min(K.x,C.x);N=Math.min(K.y,C.y);T=Math.max(K.x,C.x);S=Math.max(K.y,C.y);M=T;L=S;for(y=N;y<=L;y++){R=f[y][O];if(!R.real){if(O-(R.colspan-1)M){M=x+P}}if(Q){if(y+Q>L){L=y+Q}}}}}F.removeClass(F.select("td.mceSelected,th.mceSelected"),"mceSelected");for(y=N;y<=L;y++){for(x=O;x<=M;x++){F.addClass(f[y][x].elm,"mceSelected")}}}}c.extend(this,{deleteTable:r,split:B,merge:p,insertRow:l,insertCol:g,deleteCols:n,deleteRows:m,cutRows:D,copyRows:I,pasteRows:A,getPos:E,setStartCell:v,setEndCell:u})}c.create("tinymce.plugins.TablePlugin",{init:function(f,g){var e,k;function j(n){var m=f.selection,l=f.dom.getParent(n||m.getNode(),"table");if(l){return new a(l,f.dom,m)}}function h(){f.getBody().style.webkitUserSelect="";f.dom.removeClass(f.dom.select("td.mceSelected,th.mceSelected"),"mceSelected")}d([["table","table.desc","mceInsertTable",true],["delete_table","table.del","mceTableDelete"],["delete_col","table.delete_col_desc","mceTableDeleteCol"],["delete_row","table.delete_row_desc","mceTableDeleteRow"],["col_after","table.col_after_desc","mceTableInsertColAfter"],["col_before","table.col_before_desc","mceTableInsertColBefore"],["row_after","table.row_after_desc","mceTableInsertRowAfter"],["row_before","table.row_before_desc","mceTableInsertRowBefore"],["row_props","table.row_desc","mceTableRowProps",true],["cell_props","table.cell_desc","mceTableCellProps",true],["split_cells","table.split_cells_desc","mceTableSplitCells",true],["merge_cells","table.merge_cells_desc","mceTableMergeCells",true]],function(l){f.addButton(l[0],{title:l[1],cmd:l[2],ui:l[3]})});if(!c.isIE){f.onClick.add(function(l,m){m=m.target;if(m.nodeName==="TABLE"){l.selection.select(m)}})}f.onNodeChange.add(function(m,l,q){var o;q=m.selection.getStart();o=m.dom.getParent(q,"td,th,caption");l.setActive("table",q.nodeName==="TABLE"||!!o);if(o&&o.nodeName==="CAPTION"){o=0}l.setDisabled("delete_table",!o);l.setDisabled("delete_col",!o);l.setDisabled("delete_table",!o);l.setDisabled("delete_row",!o);l.setDisabled("col_after",!o);l.setDisabled("col_before",!o);l.setDisabled("row_after",!o);l.setDisabled("row_before",!o);l.setDisabled("row_props",!o);l.setDisabled("cell_props",!o);l.setDisabled("split_cells",!o);l.setDisabled("merge_cells",!o)});f.onInit.add(function(m){var l,p,q=m.dom,n;e=m.windowManager;m.onMouseDown.add(function(r,s){if(s.button!=2){h();p=q.getParent(s.target,"td,th");l=q.getParent(p,"table")}});q.bind(m.getDoc(),"mouseover",function(u){var s,r,t=u.target;if(p&&(n||t!=p)&&(t.nodeName=="TD"||t.nodeName=="TH")){r=q.getParent(t,"table");if(r==l){if(!n){n=j(r);n.setStartCell(p);m.getBody().style.webkitUserSelect="none"}n.setEndCell(t)}s=m.selection.getSel();if(s.removeAllRanges){s.removeAllRanges()}else{s.empty()}u.preventDefault()}});m.onMouseUp.add(function(A,B){var s,u=A.selection,C,D=u.getSel(),r,v,t,z;if(p){if(n){A.getBody().style.webkitUserSelect=""}function w(E,G){var F=new c.dom.TreeWalker(E,E);do{if(E.nodeType==3&&c.trim(E.nodeValue).length!=0){if(G){s.setStart(E,0)}else{s.setEnd(E,E.nodeValue.length)}return}if(E.nodeName=="BR"){if(G){s.setStartBefore(E)}else{s.setEndBefore(E)}return}}while(E=(G?F.next():F.prev()))}C=q.select("td.mceSelected,th.mceSelected");if(C.length>0){s=q.createRng();v=C[0];z=C[C.length-1];w(v,1);r=new c.dom.TreeWalker(v,q.getParent(C[0],"table"));do{if(v.nodeName=="TD"||v.nodeName=="TH"){if(!q.hasClass(v,"mceSelected")){break}t=v}}while(v=r.next());w(t);u.setRng(s)}A.nodeChanged();p=n=l=null}});m.onKeyUp.add(function(r,s){h()});if(m&&m.plugins.contextmenu){m.plugins.contextmenu.onContextMenu.add(function(t,r,v){var w,u=m.selection,s=u.getNode()||m.getBody();if(m.dom.getParent(v,"td")||m.dom.getParent(v,"th")||m.dom.select("td.mceSelected,th.mceSelected").length){r.removeAll();if(s.nodeName=="A"&&!m.dom.getAttrib(s,"name")){r.add({title:"advanced.link_desc",icon:"link",cmd:m.plugins.advlink?"mceAdvLink":"mceLink",ui:true});r.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"});r.addSeparator()}if(s.nodeName=="IMG"&&s.className.indexOf("mceItem")==-1){r.add({title:"advanced.image_desc",icon:"image",cmd:m.plugins.advimage?"mceAdvImage":"mceImage",ui:true});r.addSeparator()}r.add({title:"table.desc",icon:"table",cmd:"mceInsertTable",value:{action:"insert"}});r.add({title:"table.props_desc",icon:"table_props",cmd:"mceInsertTable"});r.add({title:"table.del",icon:"delete_table",cmd:"mceTableDelete"});r.addSeparator();w=r.addMenu({title:"table.cell"});w.add({title:"table.cell_desc",icon:"cell_props",cmd:"mceTableCellProps"});w.add({title:"table.split_cells_desc",icon:"split_cells",cmd:"mceTableSplitCells"});w.add({title:"table.merge_cells_desc",icon:"merge_cells",cmd:"mceTableMergeCells"});w=r.addMenu({title:"table.row"});w.add({title:"table.row_desc",icon:"row_props",cmd:"mceTableRowProps"});w.add({title:"table.row_before_desc",icon:"row_before",cmd:"mceTableInsertRowBefore"});w.add({title:"table.row_after_desc",icon:"row_after",cmd:"mceTableInsertRowAfter"});w.add({title:"table.delete_row_desc",icon:"delete_row",cmd:"mceTableDeleteRow"});w.addSeparator();w.add({title:"table.cut_row_desc",icon:"cut",cmd:"mceTableCutRow"});w.add({title:"table.copy_row_desc",icon:"copy",cmd:"mceTableCopyRow"});w.add({title:"table.paste_row_before_desc",icon:"paste",cmd:"mceTablePasteRowBefore"}).setDisabled(!k);w.add({title:"table.paste_row_after_desc",icon:"paste",cmd:"mceTablePasteRowAfter"}).setDisabled(!k);w=r.addMenu({title:"table.col"});w.add({title:"table.col_before_desc",icon:"col_before",cmd:"mceTableInsertColBefore"});w.add({title:"table.col_after_desc",icon:"col_after",cmd:"mceTableInsertColAfter"});w.add({title:"table.delete_col_desc",icon:"delete_col",cmd:"mceTableDeleteCol"})}else{r.add({title:"table.desc",icon:"table",cmd:"mceInsertTable"})}})}if(!c.isIE){function o(){var r;for(r=m.getBody().lastChild;r&&r.nodeType==3&&!r.nodeValue.length;r=r.previousSibling){}if(r&&r.nodeName=="TABLE"){m.dom.add(m.getBody(),"p",null,'
      ')}}if(c.isGecko){m.onKeyDown.add(function(s,u){var r,t,v=s.dom;if(u.keyCode==37||u.keyCode==38){r=s.selection.getRng();t=v.getParent(r.startContainer,"table");if(t&&s.getBody().firstChild==t){if(b(r,t)){r=v.createRng();r.setStartBefore(t);r.setEndBefore(t);s.selection.setRng(r);u.preventDefault()}}}})}m.onKeyUp.add(o);m.onSetContent.add(o);m.onVisualAid.add(o);m.onPreProcess.add(function(r,t){var s=t.node.lastChild;if(s&&s.childNodes.length==1&&s.firstChild.nodeName=="BR"){r.dom.remove(s)}});o()}});d({mceTableSplitCells:function(l){l.split()},mceTableMergeCells:function(m){var n,o,l;l=f.dom.getParent(f.selection.getNode(),"th,td");if(l){n=l.rowSpan;o=l.colSpan}if(!f.dom.select("td.mceSelected,th.mceSelected").length){e.open({url:g+"/merge_cells.htm",width:240+parseInt(f.getLang("table.merge_cells_delta_width",0)),height:110+parseInt(f.getLang("table.merge_cells_delta_height",0)),inline:1},{rows:n,cols:o,onaction:function(p){m.merge(l,p.cols,p.rows)},plugin_url:g})}else{m.merge()}},mceTableInsertRowBefore:function(l){l.insertRow(true)},mceTableInsertRowAfter:function(l){l.insertRow()},mceTableInsertColBefore:function(l){l.insertCol(true)},mceTableInsertColAfter:function(l){l.insertCol()},mceTableDeleteCol:function(l){l.deleteCols()},mceTableDeleteRow:function(l){l.deleteRows()},mceTableCutRow:function(l){k=l.cutRows()},mceTableCopyRow:function(l){k=l.copyRows()},mceTablePasteRowBefore:function(l){l.pasteRows(k,true)},mceTablePasteRowAfter:function(l){l.pasteRows(k)},mceTableDelete:function(l){l.deleteTable()}},function(m,l){f.addCommand(l,function(){var n=j();if(n){m(n);f.execCommand("mceRepaint");h()}})});d({mceInsertTable:function(l){e.open({url:g+"/table.htm",width:400+parseInt(f.getLang("table.table_delta_width",0)),height:320+parseInt(f.getLang("table.table_delta_height",0)),inline:1},{plugin_url:g,action:l?l.action:0})},mceTableRowProps:function(){e.open({url:g+"/row.htm",width:400+parseInt(f.getLang("table.rowprops_delta_width",0)),height:295+parseInt(f.getLang("table.rowprops_delta_height",0)),inline:1},{plugin_url:g})},mceTableCellProps:function(){e.open({url:g+"/cell.htm",width:400+parseInt(f.getLang("table.cellprops_delta_width",0)),height:295+parseInt(f.getLang("table.cellprops_delta_height",0)),inline:1},{plugin_url:g})}},function(m,l){f.addCommand(l,function(n,o){m(o)})})}});c.PluginManager.add("table",c.plugins.TablePlugin)})(tinymce); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/table/editor_plugin_src.js b/web/js/globals/tinymce/plugins/table/editor_plugin_src.js new file mode 100644 index 0000000..8c30e20 --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/editor_plugin_src.js @@ -0,0 +1,1139 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var each = tinymce.each; + + // Checks if the selection/caret is at the start of the specified block element + function isAtStart(rng, par) { + var doc = par.ownerDocument, rng2 = doc.createRange(), elm; + + rng2.setStartBefore(par); + rng2.setEnd(rng.endContainer, rng.endOffset); + + elm = doc.createElement('body'); + elm.appendChild(rng2.cloneContents()); + + // Check for text characters of other elements that should be treated as content + return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0; + }; + + /** + * Table Grid class. + */ + function TableGrid(table, dom, selection) { + var grid, startPos, endPos, selectedCell; + + buildGrid(); + selectedCell = dom.getParent(selection.getStart(), 'th,td'); + if (selectedCell) { + startPos = getPos(selectedCell); + endPos = findEndPos(); + selectedCell = getCell(startPos.x, startPos.y); + } + + function cloneNode(node, children) { + node = node.cloneNode(children); + node.removeAttribute('id'); + + return node; + } + + function buildGrid() { + var startY = 0; + + grid = []; + + each(['thead', 'tbody', 'tfoot'], function(part) { + var rows = dom.select('> ' + part + ' tr', table); + + each(rows, function(tr, y) { + y += startY; + + each(dom.select('> td, > th', tr), function(td, x) { + var x2, y2, rowspan, colspan; + + // Skip over existing cells produced by rowspan + if (grid[y]) { + while (grid[y][x]) + x++; + } + + // Get col/rowspan from cell + rowspan = getSpanVal(td, 'rowspan'); + colspan = getSpanVal(td, 'colspan'); + + // Fill out rowspan/colspan right and down + for (y2 = y; y2 < y + rowspan; y2++) { + if (!grid[y2]) + grid[y2] = []; + + for (x2 = x; x2 < x + colspan; x2++) { + grid[y2][x2] = { + part : part, + real : y2 == y && x2 == x, + elm : td, + rowspan : rowspan, + colspan : colspan + }; + } + } + }); + }); + + startY += rows.length; + }); + }; + + function getCell(x, y) { + var row; + + row = grid[y]; + if (row) + return row[x]; + }; + + function getSpanVal(td, name) { + return parseInt(td.getAttribute(name) || 1); + }; + + function isCellSelected(cell) { + return dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell; + }; + + function getSelectedRows() { + var rows = []; + + each(table.rows, function(row) { + each(row.cells, function(cell) { + if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) { + rows.push(row); + return false; + } + }); + }); + + return rows; + }; + + function deleteTable() { + var rng = dom.createRng(); + + rng.setStartAfter(table); + rng.setEndAfter(table); + + selection.setRng(rng); + + dom.remove(table); + }; + + function cloneCell(cell) { + var formatNode; + + // Clone formats + tinymce.walk(cell, function(node) { + var curNode; + + if (node.nodeType == 3) { + each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) { + node = cloneNode(node, false); + + if (!formatNode) + formatNode = curNode = node; + else if (curNode) + curNode.appendChild(node); + + curNode = node; + }); + + // Add something to the inner node + if (curNode) + curNode.innerHTML = tinymce.isIE ? ' ' : '
      '; + + return false; + } + }, 'childNodes'); + + cell = cloneNode(cell, false); + cell.rowSpan = cell.colSpan = 1; + + if (formatNode) { + cell.appendChild(formatNode); + } else { + if (!tinymce.isIE) + cell.innerHTML = '
      '; + } + + return cell; + }; + + function cleanup() { + var rng = dom.createRng(); + + // Empty rows + each(dom.select('tr', table), function(tr) { + if (tr.cells.length == 0) + dom.remove(tr); + }); + + // Empty table + if (dom.select('tr', table).length == 0) { + rng.setStartAfter(table); + rng.setEndAfter(table); + selection.setRng(rng); + dom.remove(table); + return; + } + + // Empty header/body/footer + each(dom.select('thead,tbody,tfoot', table), function(part) { + if (part.rows.length == 0) + dom.remove(part); + }); + + // Restore selection to start position if it still exists + buildGrid(); + + // Restore the selection to the closest table position + row = grid[Math.min(grid.length - 1, startPos.y)]; + if (row) { + selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); + selection.collapse(true); + } + }; + + function fillLeftDown(x, y, rows, cols) { + var tr, x2, r, c, cell; + + tr = grid[y][x].elm.parentNode; + for (r = 1; r <= rows; r++) { + tr = dom.getNext(tr, 'tr'); + + if (tr) { + // Loop left to find real cell + for (x2 = x; x2 >= 0; x2--) { + cell = grid[y + r][x2].elm; + + if (cell.parentNode == tr) { + // Append clones after + for (c = 1; c <= cols; c++) + dom.insertAfter(cloneCell(cell), cell); + + break; + } + } + + if (x2 == -1) { + // Insert nodes before first cell + for (c = 1; c <= cols; c++) + tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); + } + } + } + }; + + function split() { + each(grid, function(row, y) { + each(row, function(cell, x) { + var colSpan, rowSpan, newCell, i; + + if (isCellSelected(cell)) { + cell = cell.elm; + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan > 1 || rowSpan > 1) { + cell.colSpan = cell.rowSpan = 1; + + // Insert cells right + for (i = 0; i < colSpan - 1; i++) + dom.insertAfter(cloneCell(cell), cell); + + fillLeftDown(x, y, rowSpan - 1, colSpan); + } + } + }); + }); + }; + + function merge(cell, cols, rows) { + var startX, startY, endX, endY, x, y, startCell, endCell, cell, children; + + // Use specified cell and cols/rows + if (cell) { + pos = getPos(cell); + startX = pos.x; + startY = pos.y; + endX = startX + (cols - 1); + endY = startY + (rows - 1); + } else { + // Use selection + startX = startPos.x; + startY = startPos.y; + endX = endPos.x; + endY = endPos.y; + } + + // Find start/end cells + startCell = getCell(startX, startY); + endCell = getCell(endX, endY); + + // Check if the cells exists and if they are of the same part for example tbody = tbody + if (startCell && endCell && startCell.part == endCell.part) { + // Split and rebuild grid + split(); + buildGrid(); + + // Set row/col span to start cell + startCell = getCell(startX, startY).elm; + startCell.colSpan = (endX - startX) + 1; + startCell.rowSpan = (endY - startY) + 1; + + // Remove other cells and add it's contents to the start cell + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + cell = grid[y][x].elm; + + if (cell != startCell) { + // Move children to startCell + children = tinymce.grep(cell.childNodes); + each(children, function(node, i) { + // Jump over last BR element + if (node.nodeName != 'BR' || i != children.length - 1) + startCell.appendChild(node); + }); + + // Remove cell + dom.remove(cell); + } + } + } + + // Remove empty rows etc and restore caret location + cleanup(); + } + }; + + function insertRow(before) { + var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell; + + // Find first/last row + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell)) { + cell = cell.elm; + rowElm = cell.parentNode; + newRow = cloneNode(rowElm, false); + posY = y; + + if (before) + return false; + } + }); + + if (before) + return !posY; + }); + + for (x = 0; x < grid[0].length; x++) { + cell = grid[posY][x].elm; + + if (cell != lastCell) { + if (!before) { + rowSpan = getSpanVal(cell, 'rowspan'); + if (rowSpan > 1) { + cell.rowSpan = rowSpan + 1; + continue; + } + } else { + // Check if cell above can be expanded + if (posY > 0 && grid[posY - 1][x]) { + otherCell = grid[posY - 1][x].elm; + rowSpan = getSpanVal(otherCell, 'rowspan'); + if (rowSpan > 1) { + otherCell.rowSpan = rowSpan + 1; + continue; + } + } + } + + // Insert new cell into new row + newCell = cloneCell(cell) + newCell.colSpan = cell.colSpan; + newRow.appendChild(newCell); + + lastCell = cell; + } + } + + if (newRow.hasChildNodes()) { + if (!before) + dom.insertAfter(newRow, rowElm); + else + rowElm.parentNode.insertBefore(newRow, rowElm); + } + }; + + function insertCol(before) { + var posX, lastCell; + + // Find first/last column + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell)) { + posX = x; + + if (before) + return false; + } + }); + + if (before) + return !posX; + }); + + each(grid, function(row, y) { + var cell = row[posX].elm, rowSpan, colSpan; + + if (cell != lastCell) { + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan == 1) { + if (!before) { + dom.insertAfter(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } else { + cell.parentNode.insertBefore(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } + } else + cell.colSpan++; + + lastCell = cell; + } + }); + }; + + function deleteCols() { + var cols = []; + + // Get selected column indexes + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) { + each(grid, function(row) { + var cell = row[x].elm, colSpan; + + colSpan = getSpanVal(cell, 'colspan'); + + if (colSpan > 1) + cell.colSpan = colSpan - 1; + else + dom.remove(cell); + }); + + cols.push(x); + } + }); + }); + + cleanup(); + }; + + function deleteRows() { + var rows; + + function deleteRow(tr) { + var nextTr, pos, lastCell; + + nextTr = dom.getNext(tr, 'tr'); + + // Move down row spanned cells + each(tr.cells, function(cell) { + var rowSpan = getSpanVal(cell, 'rowspan'); + + if (rowSpan > 1) { + cell.rowSpan = rowSpan - 1; + pos = getPos(cell); + fillLeftDown(pos.x, pos.y, 1, 1); + } + }); + + // Delete cells + pos = getPos(tr.cells[0]); + each(grid[pos.y], function(cell) { + var rowSpan; + + cell = cell.elm; + + if (cell != lastCell) { + rowSpan = getSpanVal(cell, 'rowspan'); + + if (rowSpan <= 1) + dom.remove(cell); + else + cell.rowSpan = rowSpan - 1; + + lastCell = cell; + } + }); + }; + + // Get selected rows and move selection out of scope + rows = getSelectedRows(); + + // Delete all selected rows + each(rows.reverse(), function(tr) { + deleteRow(tr); + }); + + cleanup(); + }; + + function cutRows() { + var rows = getSelectedRows(); + + dom.remove(rows); + cleanup(); + + return rows; + }; + + function copyRows() { + var rows = getSelectedRows(); + + each(rows, function(row, i) { + rows[i] = cloneNode(row, true); + }); + + return rows; + }; + + function pasteRows(rows, before) { + var selectedRows = getSelectedRows(), + targetRow = selectedRows[before ? 0 : selectedRows.length - 1], + targetCellCount = targetRow.cells.length; + + // Calc target cell count + each(grid, function(row) { + var match; + + targetCellCount = 0; + each(row, function(cell, x) { + if (cell.real) + targetCellCount += cell.colspan; + + if (cell.elm.parentNode == targetRow) + match = 1; + }); + + if (match) + return false; + }); + + if (!before) + rows.reverse(); + + each(rows, function(row) { + var cellCount = row.cells.length, cell; + + // Remove col/rowspans + for (i = 0; i < cellCount; i++) { + cell = row.cells[i]; + cell.colSpan = cell.rowSpan = 1; + } + + // Needs more cells + for (i = cellCount; i < targetCellCount; i++) + row.appendChild(cloneCell(row.cells[cellCount - 1])); + + // Needs less cells + for (i = targetCellCount; i < cellCount; i++) + dom.remove(row.cells[i]); + + // Add before/after + if (before) + targetRow.parentNode.insertBefore(row, targetRow); + else + dom.insertAfter(row, targetRow); + }); + }; + + function getPos(target) { + var pos; + + each(grid, function(row, y) { + each(row, function(cell, x) { + if (cell.elm == target) { + pos = {x : x, y : y}; + return false; + } + }); + + return !pos; + }); + + return pos; + }; + + function setStartCell(cell) { + startPos = getPos(cell); + }; + + function findEndPos() { + var pos, maxX, maxY; + + maxX = maxY = 0; + + each(grid, function(row, y) { + each(row, function(cell, x) { + var colSpan, rowSpan; + + if (isCellSelected(cell)) { + cell = grid[y][x]; + + if (x > maxX) + maxX = x; + + if (y > maxY) + maxY = y; + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) + maxX = x + colSpan; + } + + if (rowSpan) { + if (y + rowSpan > maxY) + maxY = y + rowSpan; + } + } + } + }); + }); + + return {x : maxX, y : maxY}; + }; + + function setEndCell(cell) { + var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan; + + endPos = getPos(cell); + + if (startPos && endPos) { + // Get start/end positions + startX = Math.min(startPos.x, endPos.x); + startY = Math.min(startPos.y, endPos.y); + endX = Math.max(startPos.x, endPos.x); + endY = Math.max(startPos.y, endPos.y); + + // Expand end positon to include spans + maxX = endX; + maxY = endY; + + // Expand startX + for (y = startY; y <= maxY; y++) { + cell = grid[y][startX]; + + if (!cell.real) { + if (startX - (cell.colspan - 1) < startX) + startX -= cell.colspan - 1; + } + } + + // Expand startY + for (x = startX; x <= maxX; x++) { + cell = grid[startY][x]; + + if (!cell.real) { + if (startY - (cell.rowspan - 1) < startY) + startY -= cell.rowspan - 1; + } + } + + // Find max X, Y + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + cell = grid[y][x]; + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) + maxX = x + colSpan; + } + + if (rowSpan) { + if (y + rowSpan > maxY) + maxY = y + rowSpan; + } + } + } + } + + // Remove current selection + dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); + + // Add new selection + for (y = startY; y <= maxY; y++) { + for (x = startX; x <= maxX; x++) + dom.addClass(grid[y][x].elm, 'mceSelected'); + } + } + }; + + // Expose to public + tinymce.extend(this, { + deleteTable : deleteTable, + split : split, + merge : merge, + insertRow : insertRow, + insertCol : insertCol, + deleteCols : deleteCols, + deleteRows : deleteRows, + cutRows : cutRows, + copyRows : copyRows, + pasteRows : pasteRows, + getPos : getPos, + setStartCell : setStartCell, + setEndCell : setEndCell + }); + }; + + tinymce.create('tinymce.plugins.TablePlugin', { + init : function(ed, url) { + var winMan, clipboardRows; + + function createTableGrid(node) { + var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table'); + + if (tblElm) + return new TableGrid(tblElm, ed.dom, selection); + }; + + function cleanup() { + // Restore selection possibilities + ed.getBody().style.webkitUserSelect = ''; + ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); + }; + + // Register buttons + each([ + ['table', 'table.desc', 'mceInsertTable', true], + ['delete_table', 'table.del', 'mceTableDelete'], + ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'], + ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'], + ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'], + ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'], + ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'], + ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'], + ['row_props', 'table.row_desc', 'mceTableRowProps', true], + ['cell_props', 'table.cell_desc', 'mceTableCellProps', true], + ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true], + ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true] + ], function(c) { + ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]}); + }); + + // Select whole table is a table border is clicked + if (!tinymce.isIE) { + ed.onClick.add(function(ed, e) { + e = e.target; + + if (e.nodeName === 'TABLE') + ed.selection.select(e); + }); + } + + // Handle node change updates + ed.onNodeChange.add(function(ed, cm, n) { + var p; + + n = ed.selection.getStart(); + p = ed.dom.getParent(n, 'td,th,caption'); + cm.setActive('table', n.nodeName === 'TABLE' || !!p); + + // Disable table tools if we are in caption + if (p && p.nodeName === 'CAPTION') + p = 0; + + cm.setDisabled('delete_table', !p); + cm.setDisabled('delete_col', !p); + cm.setDisabled('delete_table', !p); + cm.setDisabled('delete_row', !p); + cm.setDisabled('col_after', !p); + cm.setDisabled('col_before', !p); + cm.setDisabled('row_after', !p); + cm.setDisabled('row_before', !p); + cm.setDisabled('row_props', !p); + cm.setDisabled('cell_props', !p); + cm.setDisabled('split_cells', !p); + cm.setDisabled('merge_cells', !p); + }); + + ed.onInit.add(function(ed) { + var startTable, startCell, dom = ed.dom, tableGrid; + + winMan = ed.windowManager; + + // Add cell selection logic + ed.onMouseDown.add(function(ed, e) { + if (e.button != 2) { + cleanup(); + + startCell = dom.getParent(e.target, 'td,th'); + startTable = dom.getParent(startCell, 'table'); + } + }); + + dom.bind(ed.getDoc(), 'mouseover', function(e) { + var sel, table, target = e.target; + + if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) { + table = dom.getParent(target, 'table'); + if (table == startTable) { + if (!tableGrid) { + tableGrid = createTableGrid(table); + tableGrid.setStartCell(startCell); + + ed.getBody().style.webkitUserSelect = 'none'; + } + + tableGrid.setEndCell(target); + } + + // Remove current selection + sel = ed.selection.getSel(); + + if (sel.removeAllRanges) + sel.removeAllRanges(); + else + sel.empty(); + + e.preventDefault(); + } + }); + + ed.onMouseUp.add(function(ed, e) { + var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode; + + // Move selection to startCell + if (startCell) { + if (tableGrid) + ed.getBody().style.webkitUserSelect = ''; + + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; + + // Try to expand text selection as much as we can only Gecko supports cell selection + selectedCells = dom.select('td.mceSelected,th.mceSelected'); + if (selectedCells.length > 0) { + rng = dom.createRng(); + node = selectedCells[0]; + endNode = selectedCells[selectedCells.length - 1]; + + setPoint(node, 1); + walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table')); + + do { + if (node.nodeName == 'TD' || node.nodeName == 'TH') { + if (!dom.hasClass(node, 'mceSelected')) + break; + + lastNode = node; + } + } while (node = walker.next()); + + setPoint(lastNode); + + sel.setRng(rng); + } + + ed.nodeChanged(); + startCell = tableGrid = startTable = null; + } + }); + + ed.onKeyUp.add(function(ed, e) { + cleanup(); + }); + + // Add context menu + if (ed && ed.plugins.contextmenu) { + ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) { + var sm, se = ed.selection, el = se.getNode() || ed.getBody(); + + if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) { + m.removeAll(); + + if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) { + m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); + m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); + m.addSeparator(); + } + + if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) { + m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); + m.addSeparator(); + } + + m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}}); + m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'}); + m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'}); + m.addSeparator(); + + // Cell menu + sm = m.addMenu({title : 'table.cell'}); + sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'}); + sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'}); + sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'}); + + // Row menu + sm = m.addMenu({title : 'table.row'}); + sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'}); + sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'}); + sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'}); + sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'}); + sm.addSeparator(); + sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'}); + sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'}); + sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows); + sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows); + + // Column menu + sm = m.addMenu({title : 'table.col'}); + sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'}); + sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'}); + sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'}); + } else + m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'}); + }); + } + + // Fixes an issue on Gecko where it's impossible to place the caret behind a table + // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled + if (!tinymce.isIE) { + function fixTableCaretPos() { + var last; + + // Skip empty text nodes form the end + for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ; + + if (last && last.nodeName == 'TABLE') + ed.dom.add(ed.getBody(), 'p', null, '
      '); + }; + + // Fixes an bug where it's impossible to place the caret before a table in Gecko + // this fix solves it by detecting when the caret is at the beginning of such a table + // and then manually moves the caret infront of the table + if (tinymce.isGecko) { + ed.onKeyDown.add(function(ed, e) { + var rng, table, dom = ed.dom; + + // On gecko it's not possible to place the caret before a table + if (e.keyCode == 37 || e.keyCode == 38) { + rng = ed.selection.getRng(); + table = dom.getParent(rng.startContainer, 'table'); + + if (table && ed.getBody().firstChild == table) { + if (isAtStart(rng, table)) { + rng = dom.createRng(); + + rng.setStartBefore(table); + rng.setEndBefore(table); + + ed.selection.setRng(rng); + + e.preventDefault(); + } + } + } + }); + } + + ed.onKeyUp.add(fixTableCaretPos); + ed.onSetContent.add(fixTableCaretPos); + ed.onVisualAid.add(fixTableCaretPos); + + ed.onPreProcess.add(function(ed, o) { + var last = o.node.lastChild; + + if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR') + ed.dom.remove(last); + }); + + fixTableCaretPos(); + } + }); + + // Register action commands + each({ + mceTableSplitCells : function(grid) { + grid.split(); + }, + + mceTableMergeCells : function(grid) { + var rowSpan, colSpan, cell; + + cell = ed.dom.getParent(ed.selection.getNode(), 'th,td'); + if (cell) { + rowSpan = cell.rowSpan; + colSpan = cell.colSpan; + } + + if (!ed.dom.select('td.mceSelected,th.mceSelected').length) { + winMan.open({ + url : url + '/merge_cells.htm', + width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)), + height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)), + inline : 1 + }, { + rows : rowSpan, + cols : colSpan, + onaction : function(data) { + grid.merge(cell, data.cols, data.rows); + }, + plugin_url : url + }); + } else + grid.merge(); + }, + + mceTableInsertRowBefore : function(grid) { + grid.insertRow(true); + }, + + mceTableInsertRowAfter : function(grid) { + grid.insertRow(); + }, + + mceTableInsertColBefore : function(grid) { + grid.insertCol(true); + }, + + mceTableInsertColAfter : function(grid) { + grid.insertCol(); + }, + + mceTableDeleteCol : function(grid) { + grid.deleteCols(); + }, + + mceTableDeleteRow : function(grid) { + grid.deleteRows(); + }, + + mceTableCutRow : function(grid) { + clipboardRows = grid.cutRows(); + }, + + mceTableCopyRow : function(grid) { + clipboardRows = grid.copyRows(); + }, + + mceTablePasteRowBefore : function(grid) { + grid.pasteRows(clipboardRows, true); + }, + + mceTablePasteRowAfter : function(grid) { + grid.pasteRows(clipboardRows); + }, + + mceTableDelete : function(grid) { + grid.deleteTable(); + } + }, function(func, name) { + ed.addCommand(name, function() { + var grid = createTableGrid(); + + if (grid) { + func(grid); + ed.execCommand('mceRepaint'); + cleanup(); + } + }); + }); + + // Register dialog commands + each({ + mceInsertTable : function(val) { + winMan.open({ + url : url + '/table.htm', + width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)), + height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + action : val ? val.action : 0 + }); + }, + + mceTableRowProps : function() { + winMan.open({ + url : url + '/row.htm', + width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)), + height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }, + + mceTableCellProps : function() { + winMan.open({ + url : url + '/cell.htm', + width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)), + height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + } + }, function(func, name) { + ed.addCommand(name, function(ui, val) { + func(val); + }); + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin); +})(tinymce); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/table/js/cell.js b/web/js/globals/tinymce/plugins/table/js/cell.js new file mode 100644 index 0000000..b5fc1fd --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/js/cell.js @@ -0,0 +1,286 @@ +tinyMCEPopup.requireLangPack(); + +var ed; + +function init() { + ed = tinyMCEPopup.editor; + tinyMCEPopup.resizeToInnerSize(); + + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor'); + document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor') + + var inst = ed; + var tdElm = ed.dom.getParent(ed.selection.getStart(), "td,th"); + var formObj = document.forms[0]; + var st = ed.dom.parseStyle(ed.dom.getAttrib(tdElm, "style")); + + // Get table cell data + var celltype = tdElm.nodeName.toLowerCase(); + var align = ed.dom.getAttrib(tdElm, 'align'); + var valign = ed.dom.getAttrib(tdElm, 'valign'); + var width = trimSize(getStyle(tdElm, 'width', 'width')); + var height = trimSize(getStyle(tdElm, 'height', 'height')); + var bordercolor = convertRGBToHex(getStyle(tdElm, 'bordercolor', 'borderLeftColor')); + var bgcolor = convertRGBToHex(getStyle(tdElm, 'bgcolor', 'backgroundColor')); + var className = ed.dom.getAttrib(tdElm, 'class'); + var backgroundimage = getStyle(tdElm, 'background', 'backgroundImage').replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + var id = ed.dom.getAttrib(tdElm, 'id'); + var lang = ed.dom.getAttrib(tdElm, 'lang'); + var dir = ed.dom.getAttrib(tdElm, 'dir'); + var scope = ed.dom.getAttrib(tdElm, 'scope'); + + // Setup form + addClassesToList('class', 'table_cell_styles'); + TinyMCE_EditableSelects.init(); + + if (!ed.dom.hasClass(tdElm, 'mceSelected')) { + formObj.bordercolor.value = bordercolor; + formObj.bgcolor.value = bgcolor; + formObj.backgroundimage.value = backgroundimage; + formObj.width.value = width; + formObj.height.value = height; + formObj.id.value = id; + formObj.lang.value = lang; + formObj.style.value = ed.dom.serializeStyle(st); + selectByValue(formObj, 'align', align); + selectByValue(formObj, 'valign', valign); + selectByValue(formObj, 'class', className, true, true); + selectByValue(formObj, 'celltype', celltype); + selectByValue(formObj, 'dir', dir); + selectByValue(formObj, 'scope', scope); + + // Resize some elements + if (isVisible('backgroundimagebrowser')) + document.getElementById('backgroundimage').style.width = '180px'; + + updateColor('bordercolor_pick', 'bordercolor'); + updateColor('bgcolor_pick', 'bgcolor'); + } else + tinyMCEPopup.dom.hide('action'); +} + +function updateAction() { + var el, inst = ed, tdElm, trElm, tableElm, formObj = document.forms[0]; + + tinyMCEPopup.restoreSelection(); + el = ed.selection.getStart(); + tdElm = ed.dom.getParent(el, "td,th"); + trElm = ed.dom.getParent(el, "tr"); + tableElm = ed.dom.getParent(el, "table"); + + // Cell is selected + if (ed.dom.hasClass(tdElm, 'mceSelected')) { + // Update all selected sells + tinymce.each(ed.dom.select('td.mceSelected,th.mceSelected'), function(td) { + updateCell(td); + }); + + ed.addVisual(); + ed.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + tinyMCEPopup.close(); + return; + } + + ed.execCommand('mceBeginUndoLevel'); + + switch (getSelectValue(formObj, 'action')) { + case "cell": + var celltype = getSelectValue(formObj, 'celltype'); + var scope = getSelectValue(formObj, 'scope'); + + function doUpdate(s) { + if (s) { + updateCell(tdElm); + + ed.addVisual(); + ed.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + tinyMCEPopup.close(); + } + }; + + if (ed.getParam("accessibility_warnings", 1)) { + if (celltype == "th" && scope == "") + tinyMCEPopup.confirm(ed.getLang('table_dlg.missing_scope', '', true), doUpdate); + else + doUpdate(1); + + return; + } + + updateCell(tdElm); + break; + + case "row": + var cell = trElm.firstChild; + + if (cell.nodeName != "TD" && cell.nodeName != "TH") + cell = nextCell(cell); + + do { + cell = updateCell(cell, true); + } while ((cell = nextCell(cell)) != null); + + break; + + case "all": + var rows = tableElm.getElementsByTagName("tr"); + + for (var i=0; i 0) { + tinymce.each(tableElm.rows, function(tr) { + var i; + + for (i = 0; i < tr.cells.length; i++) { + if (dom.hasClass(tr.cells[i], 'mceSelected')) { + updateRow(tr, true); + return; + } + } + }); + + inst.addVisual(); + inst.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + tinyMCEPopup.close(); + return; + } + + inst.execCommand('mceBeginUndoLevel'); + + switch (action) { + case "row": + updateRow(trElm); + break; + + case "all": + var rows = tableElm.getElementsByTagName("tr"); + + for (var i=0; i colLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.col_limit').replace(/\{\$cols\}/g, colLimit)); + return false; + } else if (rowLimit && rows > rowLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.row_limit').replace(/\{\$rows\}/g, rowLimit)); + return false; + } else if (cellLimit && cols * rows > cellLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.cell_limit').replace(/\{\$cells\}/g, cellLimit)); + return false; + } + + // Update table + if (action == "update") { + inst.execCommand('mceBeginUndoLevel'); + + dom.setAttrib(elm, 'cellPadding', cellpadding, true); + dom.setAttrib(elm, 'cellSpacing', cellspacing, true); + dom.setAttrib(elm, 'border', border); + dom.setAttrib(elm, 'align', align); + dom.setAttrib(elm, 'frame', frame); + dom.setAttrib(elm, 'rules', rules); + dom.setAttrib(elm, 'class', className); + dom.setAttrib(elm, 'style', style); + dom.setAttrib(elm, 'id', id); + dom.setAttrib(elm, 'summary', summary); + dom.setAttrib(elm, 'dir', dir); + dom.setAttrib(elm, 'lang', lang); + + capEl = inst.dom.select('caption', elm)[0]; + + if (capEl && !caption) + capEl.parentNode.removeChild(capEl); + + if (!capEl && caption) { + capEl = elm.ownerDocument.createElement('caption'); + + if (!tinymce.isIE) + capEl.innerHTML = '
      '; + + elm.insertBefore(capEl, elm.firstChild); + } + + if (width && inst.settings.inline_styles) { + dom.setStyle(elm, 'width', width); + dom.setAttrib(elm, 'width', ''); + } else { + dom.setAttrib(elm, 'width', width, true); + dom.setStyle(elm, 'width', ''); + } + + // Remove these since they are not valid XHTML + dom.setAttrib(elm, 'borderColor', ''); + dom.setAttrib(elm, 'bgColor', ''); + dom.setAttrib(elm, 'background', ''); + + if (height && inst.settings.inline_styles) { + dom.setStyle(elm, 'height', height); + dom.setAttrib(elm, 'height', ''); + } else { + dom.setAttrib(elm, 'height', height, true); + dom.setStyle(elm, 'height', ''); + } + + if (background != '') + elm.style.backgroundImage = "url('" + background + "')"; + else + elm.style.backgroundImage = ''; + +/* if (tinyMCEPopup.getParam("inline_styles")) { + if (width != '') + elm.style.width = getCSSSize(width); + }*/ + + if (bordercolor != "") { + elm.style.borderColor = bordercolor; + elm.style.borderStyle = elm.style.borderStyle == "" ? "solid" : elm.style.borderStyle; + elm.style.borderWidth = border == "" ? "1px" : border; + } else + elm.style.borderColor = ''; + + elm.style.backgroundColor = bgcolor; + elm.style.height = getCSSSize(height); + + inst.addVisual(); + + // Fix for stange MSIE align bug + //elm.outerHTML = elm.outerHTML; + + inst.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + + // Repaint if dimensions changed + if (formObj.width.value != orgTableWidth || formObj.height.value != orgTableHeight) + inst.execCommand('mceRepaint'); + + tinyMCEPopup.close(); + return true; + } + + // Create new table + html += ''); + + tinymce.each('h1,h2,h3,h4,h5,h6,p'.split(','), function(n) { + if (patt) + patt += ','; + + patt += n + ' ._mce_marker'; + }); + + tinymce.each(inst.dom.select(patt), function(n) { + inst.dom.split(inst.dom.getParent(n, 'h1,h2,h3,h4,h5,h6,p'), n); + }); + + dom.setOuterHTML(dom.select('br._mce_marker')[0], html); + } else + inst.execCommand('mceInsertContent', false, html); + + tinymce.each(dom.select('table[_mce_new]'), function(node) { + var td = dom.select('td', node); + + try { + // IE9 might fail to do this selection + inst.selection.select(td[0], true); + inst.selection.collapse(); + } catch (ex) { + // Ignore + } + + dom.setAttrib(node, '_mce_new', ''); + }); + + inst.addVisual(); + inst.execCommand('mceEndUndoLevel'); + + tinyMCEPopup.close(); +} + +function makeAttrib(attrib, value) { + var formObj = document.forms[0]; + var valueElm = formObj.elements[attrib]; + + if (typeof(value) == "undefined" || value == null) { + value = ""; + + if (valueElm) + value = valueElm.value; + } + + if (value == "") + return ""; + + // XML encode it + value = value.replace(/&/g, '&'); + value = value.replace(/\"/g, '"'); + value = value.replace(//g, '>'); + + return ' ' + attrib + '="' + value + '"'; +} + +function init() { + tinyMCEPopup.resizeToInnerSize(); + + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor'); + document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); + + var cols = 2, rows = 2, border = tinyMCEPopup.getParam('table_default_border', '0'), cellpadding = tinyMCEPopup.getParam('table_default_cellpadding', ''), cellspacing = tinyMCEPopup.getParam('table_default_cellspacing', ''); + var align = "", width = "", height = "", bordercolor = "", bgcolor = "", className = ""; + var id = "", summary = "", style = "", dir = "", lang = "", background = "", bgcolor = "", bordercolor = "", rules = "", frame = ""; + var inst = tinyMCEPopup.editor, dom = inst.dom; + var formObj = document.forms[0]; + var elm = dom.getParent(inst.selection.getNode(), "table"); + + action = tinyMCEPopup.getWindowArg('action'); + + if (!action) + action = elm ? "update" : "insert"; + + if (elm && action != "insert") { + var rowsAr = elm.rows; + var cols = 0; + for (var i=0; i cols) + cols = rowsAr[i].cells.length; + + cols = cols; + rows = rowsAr.length; + + st = dom.parseStyle(dom.getAttrib(elm, "style")); + border = trimSize(getStyle(elm, 'border', 'borderWidth')); + cellpadding = dom.getAttrib(elm, 'cellpadding', ""); + cellspacing = dom.getAttrib(elm, 'cellspacing', ""); + width = trimSize(getStyle(elm, 'width', 'width')); + height = trimSize(getStyle(elm, 'height', 'height')); + bordercolor = convertRGBToHex(getStyle(elm, 'bordercolor', 'borderLeftColor')); + bgcolor = convertRGBToHex(getStyle(elm, 'bgcolor', 'backgroundColor')); + align = dom.getAttrib(elm, 'align', align); + frame = dom.getAttrib(elm, 'frame'); + rules = dom.getAttrib(elm, 'rules'); + className = tinymce.trim(dom.getAttrib(elm, 'class').replace(/mceItem.+/g, '')); + id = dom.getAttrib(elm, 'id'); + summary = dom.getAttrib(elm, 'summary'); + style = dom.serializeStyle(st); + dir = dom.getAttrib(elm, 'dir'); + lang = dom.getAttrib(elm, 'lang'); + background = getStyle(elm, 'background', 'backgroundImage').replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + formObj.caption.checked = elm.getElementsByTagName('caption').length > 0; + + orgTableWidth = width; + orgTableHeight = height; + + action = "update"; + formObj.insert.value = inst.getLang('update'); + } + + addClassesToList('class', "table_styles"); + TinyMCE_EditableSelects.init(); + + // Update form + selectByValue(formObj, 'align', align); + selectByValue(formObj, 'tframe', frame); + selectByValue(formObj, 'rules', rules); + selectByValue(formObj, 'class', className, true, true); + formObj.cols.value = cols; + formObj.rows.value = rows; + formObj.border.value = border; + formObj.cellpadding.value = cellpadding; + formObj.cellspacing.value = cellspacing; + formObj.width.value = width; + formObj.height.value = height; + formObj.bordercolor.value = bordercolor; + formObj.bgcolor.value = bgcolor; + formObj.id.value = id; + formObj.summary.value = summary; + formObj.style.value = style; + formObj.dir.value = dir; + formObj.lang.value = lang; + formObj.backgroundimage.value = background; + + updateColor('bordercolor_pick', 'bordercolor'); + updateColor('bgcolor_pick', 'bgcolor'); + + // Resize some elements + if (isVisible('backgroundimagebrowser')) + document.getElementById('backgroundimage').style.width = '180px'; + + // Disable some fields in update mode + if (action == "update") { + formObj.cols.disabled = true; + formObj.rows.disabled = true; + } +} + +function changedSize() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + +/* var width = formObj.width.value; + if (width != "") + st['width'] = tinyMCEPopup.getParam("inline_styles") ? getCSSSize(width) : ""; + else + st['width'] = "";*/ + + var height = formObj.height.value; + if (height != "") + st['height'] = getCSSSize(height); + else + st['height'] = ""; + + formObj.style.value = dom.serializeStyle(st); +} + +function changedBackgroundImage() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + st['background-image'] = "url('" + formObj.backgroundimage.value + "')"; + + formObj.style.value = dom.serializeStyle(st); +} + +function changedBorder() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + // Update border width if the element has a color + if (formObj.border.value != "" && formObj.bordercolor.value != "") + st['border-width'] = formObj.border.value + "px"; + + formObj.style.value = dom.serializeStyle(st); +} + +function changedColor() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + st['background-color'] = formObj.bgcolor.value; + + if (formObj.bordercolor.value != "") { + st['border-color'] = formObj.bordercolor.value; + + // Add border-width if it's missing + if (!st['border-width']) + st['border-width'] = formObj.border.value == "" ? "1px" : formObj.border.value + "px"; + } + + formObj.style.value = dom.serializeStyle(st); +} + +function changedStyle() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + if (st['background-image']) + formObj.backgroundimage.value = st['background-image'].replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + else + formObj.backgroundimage.value = ''; + + if (st['width']) + formObj.width.value = trimSize(st['width']); + + if (st['height']) + formObj.height.value = trimSize(st['height']); + + if (st['background-color']) { + formObj.bgcolor.value = st['background-color']; + updateColor('bgcolor_pick','bgcolor'); + } + + if (st['border-color']) { + formObj.bordercolor.value = st['border-color']; + updateColor('bordercolor_pick','bordercolor'); + } +} + +tinyMCEPopup.onInit.add(init); diff --git a/web/js/globals/tinymce/plugins/table/langs/en_dlg.js b/web/js/globals/tinymce/plugins/table/langs/en_dlg.js new file mode 100644 index 0000000..000332a --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/langs/en_dlg.js @@ -0,0 +1,74 @@ +tinyMCE.addI18n('en.table_dlg',{ +general_tab:"General", +advanced_tab:"Advanced", +general_props:"General properties", +advanced_props:"Advanced properties", +rowtype:"Row in table part", +title:"Insert/Modify table", +width:"Width", +height:"Height", +cols:"Cols", +rows:"Rows", +cellspacing:"Cellspacing", +cellpadding:"Cellpadding", +border:"Border", +align:"Alignment", +align_default:"Default", +align_left:"Left", +align_right:"Right", +align_middle:"Center", +row_title:"Table row properties", +cell_title:"Table cell properties", +cell_type:"Cell type", +valign:"Vertical alignment", +align_top:"Top", +align_bottom:"Bottom", +bordercolor:"Border color", +bgcolor:"Background color", +merge_cells_title:"Merge table cells", +id:"Id", +style:"Style", +langdir:"Language direction", +langcode:"Language code", +mime:"Target MIME type", +ltr:"Left to right", +rtl:"Right to left", +bgimage:"Background image", +summary:"Summary", +td:"Data", +th:"Header", +cell_cell:"Update current cell", +cell_row:"Update all cells in row", +cell_all:"Update all cells in table", +row_row:"Update current row", +row_odd:"Update odd rows in table", +row_even:"Update even rows in table", +row_all:"Update all rows in table", +thead:"Table Head", +tbody:"Table Body", +tfoot:"Table Foot", +scope:"Scope", +rowgroup:"Row Group", +colgroup:"Col Group", +col_limit:"You've exceeded the maximum number of columns of {$cols}.", +row_limit:"You've exceeded the maximum number of rows of {$rows}.", +cell_limit:"You've exceeded the maximum number of cells of {$cells}.", +missing_scope:"Are you sure you want to continue without specifying a scope for this table header cell. Without it, it may be difficult for some users with disabilities to understand the content or data displayed of the table.", +caption:"Table caption", +frame:"Frame", +frame_none:"none", +frame_groups:"groups", +frame_rows:"rows", +frame_cols:"cols", +frame_all:"all", +rules:"Rules", +rules_void:"void", +rules_above:"above", +rules_below:"below", +rules_hsides:"hsides", +rules_lhs:"lhs", +rules_rhs:"rhs", +rules_vsides:"vsides", +rules_box:"box", +rules_border:"border" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/table/merge_cells.htm b/web/js/globals/tinymce/plugins/table/merge_cells.htm new file mode 100644 index 0000000..9736ed8 --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/merge_cells.htm @@ -0,0 +1,32 @@ + + + + {#table_dlg.merge_cells_title} + + + + + + +
      +
      + {#table_dlg.merge_cells_title} + + + + + + + + + +
      {#table_dlg.cols}:
      {#table_dlg.rows}:
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/table/row.htm b/web/js/globals/tinymce/plugins/table/row.htm new file mode 100644 index 0000000..092e6c8 --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/row.htm @@ -0,0 +1,155 @@ + + + + {#table_dlg.row_title} + + + + + + + + +
      + + +
      +
      +
      + {#table_dlg.general_props} + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + +
      + +
      + +
      +
      +
      + +
      +
      + {#table_dlg.advanced_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + +
      + + + + + +
       
      +
      + + + + + +
       
      +
      +
      +
      +
      + +
      +
      + +
      + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/table/table.htm b/web/js/globals/tinymce/plugins/table/table.htm new file mode 100644 index 0000000..f269039 --- /dev/null +++ b/web/js/globals/tinymce/plugins/table/table.htm @@ -0,0 +1,187 @@ + + + + {#table_dlg.title} + + + + + + + + + +
      + + +
      +
      +
      + {#table_dlg.general_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +
      + {#table_dlg.advanced_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + +
       
      +
      + +
      + +
      + +
      + + + + + +
       
      +
      + + + + + +
       
      +
      +
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/template/blank.htm b/web/js/globals/tinymce/plugins/template/blank.htm new file mode 100644 index 0000000..ecde53f --- /dev/null +++ b/web/js/globals/tinymce/plugins/template/blank.htm @@ -0,0 +1,12 @@ + + + blank_page + + + + + + + diff --git a/web/js/globals/tinymce/plugins/template/css/template.css b/web/js/globals/tinymce/plugins/template/css/template.css new file mode 100644 index 0000000..2d23a49 --- /dev/null +++ b/web/js/globals/tinymce/plugins/template/css/template.css @@ -0,0 +1,23 @@ +#frmbody { + padding: 10px; + background-color: #FFF; + border: 1px solid #CCC; +} + +.frmRow { + margin-bottom: 10px; +} + +#templatesrc { + border: none; + width: 320px; + height: 240px; +} + +.title { + padding-bottom: 5px; +} + +.mceActionPanel { + padding-top: 5px; +} diff --git a/web/js/globals/tinymce/plugins/template/editor_plugin.js b/web/js/globals/tinymce/plugins/template/editor_plugin.js new file mode 100644 index 0000000..ebe3c27 --- /dev/null +++ b/web/js/globals/tinymce/plugins/template/editor_plugin.js @@ -0,0 +1 @@ +(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.TemplatePlugin",{init:function(b,c){var d=this;d.editor=b;b.addCommand("mceTemplate",function(e){b.windowManager.open({file:c+"/template.htm",width:b.getParam("template_popup_width",750),height:b.getParam("template_popup_height",600),inline:1},{plugin_url:c})});b.addCommand("mceInsertTemplate",d._insertTemplate,d);b.addButton("template",{title:"template.desc",cmd:"mceTemplate"});b.onPreProcess.add(function(e,g){var f=e.dom;a(f.select("div",g.node),function(h){if(f.hasClass(h,"mceTmpl")){a(f.select("*",h),function(i){if(f.hasClass(i,e.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))){i.innerHTML=d._getDateTime(new Date(),e.getParam("template_mdate_format",e.getLang("template.mdate_format")))}});d._replaceVals(h)}})})},getInfo:function(){return{longname:"Template plugin",author:"Moxiecode Systems AB",authorurl:"http://www.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/template",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_insertTemplate:function(i,j){var k=this,g=k.editor,f,c,d=g.dom,b=g.selection.getContent();f=j.content;a(k.editor.getParam("template_replace_values"),function(l,h){if(typeof(l)!="function"){f=f.replace(new RegExp("\\{\\$"+h+"\\}","g"),l)}});c=d.create("div",null,f);n=d.select(".mceTmpl",c);if(n&&n.length>0){c=d.create("div",null);c.appendChild(n[0].cloneNode(true))}function e(l,h){return new RegExp("\\b"+h+"\\b","g").test(l.className)}a(d.select("*",c),function(h){if(e(h,g.getParam("template_cdate_classes","cdate").replace(/\s+/g,"|"))){h.innerHTML=k._getDateTime(new Date(),g.getParam("template_cdate_format",g.getLang("template.cdate_format")))}if(e(h,g.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))){h.innerHTML=k._getDateTime(new Date(),g.getParam("template_mdate_format",g.getLang("template.mdate_format")))}if(e(h,g.getParam("template_selected_content_classes","selcontent").replace(/\s+/g,"|"))){h.innerHTML=b}});k._replaceVals(c);g.execCommand("mceInsertContent",false,c.innerHTML);g.addVisual()},_replaceVals:function(c){var d=this.editor.dom,b=this.editor.getParam("template_replace_values");a(d.select("*",c),function(f){a(b,function(g,e){if(d.hasClass(f,e)){if(typeof(b[e])=="function"){b[e](f)}}})})},_getDateTime:function(e,b){if(!b){return""}function c(g,d){var f;g=""+g;if(g.length 0) { + el = dom.create('div', null); + el.appendChild(n[0].cloneNode(true)); + } + + function hasClass(n, c) { + return new RegExp('\\b' + c + '\\b', 'g').test(n.className); + }; + + each(dom.select('*', el), function(n) { + // Replace cdate + if (hasClass(n, ed.getParam('template_cdate_classes', 'cdate').replace(/\s+/g, '|'))) + n.innerHTML = t._getDateTime(new Date(), ed.getParam("template_cdate_format", ed.getLang("template.cdate_format"))); + + // Replace mdate + if (hasClass(n, ed.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|'))) + n.innerHTML = t._getDateTime(new Date(), ed.getParam("template_mdate_format", ed.getLang("template.mdate_format"))); + + // Replace selection + if (hasClass(n, ed.getParam('template_selected_content_classes', 'selcontent').replace(/\s+/g, '|'))) + n.innerHTML = sel; + }); + + t._replaceVals(el); + + ed.execCommand('mceInsertContent', false, el.innerHTML); + ed.addVisual(); + }, + + _replaceVals : function(e) { + var dom = this.editor.dom, vl = this.editor.getParam('template_replace_values'); + + each(dom.select('*', e), function(e) { + each(vl, function(v, k) { + if (dom.hasClass(e, k)) { + if (typeof(vl[k]) == 'function') + vl[k](e); + } + }); + }); + }, + + _getDateTime : function(d, fmt) { + if (!fmt) + return ""; + + function addZeros(value, len) { + var i; + + value = "" + value; + + if (value.length < len) { + for (i=0; i<(len-value.length); i++) + value = "0" + value; + } + + return value; + } + + fmt = fmt.replace("%D", "%m/%d/%y"); + fmt = fmt.replace("%r", "%I:%M:%S %p"); + fmt = fmt.replace("%Y", "" + d.getFullYear()); + fmt = fmt.replace("%y", "" + d.getYear()); + fmt = fmt.replace("%m", addZeros(d.getMonth()+1, 2)); + fmt = fmt.replace("%d", addZeros(d.getDate(), 2)); + fmt = fmt.replace("%H", "" + addZeros(d.getHours(), 2)); + fmt = fmt.replace("%M", "" + addZeros(d.getMinutes(), 2)); + fmt = fmt.replace("%S", "" + addZeros(d.getSeconds(), 2)); + fmt = fmt.replace("%I", "" + ((d.getHours() + 11) % 12 + 1)); + fmt = fmt.replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM")); + fmt = fmt.replace("%B", "" + this.editor.getLang("template_months_long").split(',')[d.getMonth()]); + fmt = fmt.replace("%b", "" + this.editor.getLang("template_months_short").split(',')[d.getMonth()]); + fmt = fmt.replace("%A", "" + this.editor.getLang("template_day_long").split(',')[d.getDay()]); + fmt = fmt.replace("%a", "" + this.editor.getLang("template_day_short").split(',')[d.getDay()]); + fmt = fmt.replace("%%", "%"); + + return fmt; + } + }); + + // Register plugin + tinymce.PluginManager.add('template', tinymce.plugins.TemplatePlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/template/js/template.js b/web/js/globals/tinymce/plugins/template/js/template.js new file mode 100644 index 0000000..24045d7 --- /dev/null +++ b/web/js/globals/tinymce/plugins/template/js/template.js @@ -0,0 +1,106 @@ +tinyMCEPopup.requireLangPack(); + +var TemplateDialog = { + preInit : function() { + var url = tinyMCEPopup.getParam("template_external_list_url"); + + if (url != null) + document.write(''); + }, + + init : function() { + var ed = tinyMCEPopup.editor, tsrc, sel, x, u; + + tsrc = ed.getParam("template_templates", false); + sel = document.getElementById('tpath'); + + // Setup external template list + if (!tsrc && typeof(tinyMCETemplateList) != 'undefined') { + for (x=0, tsrc = []; x'); + }); + }, + + selectTemplate : function(u, ti) { + var d = window.frames['templatesrc'].document, x, tsrc = this.tsrc; + + if (!u) + return; + + d.body.innerHTML = this.templateHTML = this.getFileContents(u); + + for (x=0; x + + {#template_dlg.title} + + + + + +
      +
      +
      {#template_dlg.desc}
      +
      + +
      +
      +
      +
      + {#template_dlg.preview} + +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/visualchars/editor_plugin.js b/web/js/globals/tinymce/plugins/visualchars/editor_plugin.js new file mode 100644 index 0000000..94719f9 --- /dev/null +++ b/web/js/globals/tinymce/plugins/visualchars/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.VisualChars",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceVisualChars",c._toggleVisualChars,c);a.addButton("visualchars",{title:"visualchars.desc",cmd:"mceVisualChars"});a.onBeforeGetContent.add(function(d,e){if(c.state&&e.format!="raw"&&!e.draft){c.state=true;c._toggleVisualChars(false)}})},getInfo:function(){return{longname:"Visual characters",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/visualchars",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_toggleVisualChars:function(m){var p=this,k=p.editor,a,g,j,n=k.getDoc(),o=k.getBody(),l,q=k.selection,e,c,f;p.state=!p.state;k.controlManager.setActive("visualchars",p.state);if(m){f=q.getBookmark()}if(p.state){a=[];tinymce.walk(o,function(b){if(b.nodeType==3&&b.nodeValue&&b.nodeValue.indexOf("\u00a0")!=-1){a.push(b)}},"childNodes");for(g=0;g$1');c=k.dom.create("div",null,l);while(node=c.lastChild){k.dom.insertAfter(node,a[g])}k.dom.remove(a[g])}}else{a=k.dom.select("span.mceItemNbsp",o);for(g=a.length-1;g>=0;g--){k.dom.remove(a[g],1)}}q.moveToBookmark(f)}});tinymce.PluginManager.add("visualchars",tinymce.plugins.VisualChars)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/visualchars/editor_plugin_src.js b/web/js/globals/tinymce/plugins/visualchars/editor_plugin_src.js new file mode 100644 index 0000000..35856e2 --- /dev/null +++ b/web/js/globals/tinymce/plugins/visualchars/editor_plugin_src.js @@ -0,0 +1,83 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.VisualChars', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceVisualChars', t._toggleVisualChars, t); + + // Register buttons + ed.addButton('visualchars', {title : 'visualchars.desc', cmd : 'mceVisualChars'}); + + ed.onBeforeGetContent.add(function(ed, o) { + if (t.state && o.format != 'raw' && !o.draft) { + t.state = true; + t._toggleVisualChars(false); + } + }); + }, + + getInfo : function() { + return { + longname : 'Visual characters', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/visualchars', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + _toggleVisualChars : function(bookmark) { + var t = this, ed = t.editor, nl, i, h, d = ed.getDoc(), b = ed.getBody(), nv, s = ed.selection, bo, div, bm; + + t.state = !t.state; + ed.controlManager.setActive('visualchars', t.state); + + if (bookmark) + bm = s.getBookmark(); + + if (t.state) { + nl = []; + tinymce.walk(b, function(n) { + if (n.nodeType == 3 && n.nodeValue && n.nodeValue.indexOf('\u00a0') != -1) + nl.push(n); + }, 'childNodes'); + + for (i = 0; i < nl.length; i++) { + nv = nl[i].nodeValue; + nv = nv.replace(/(\u00a0)/g, '$1'); + + div = ed.dom.create('div', null, nv); + while (node = div.lastChild) + ed.dom.insertAfter(node, nl[i]); + + ed.dom.remove(nl[i]); + } + } else { + nl = ed.dom.select('span.mceItemNbsp', b); + + for (i = nl.length - 1; i >= 0; i--) + ed.dom.remove(nl[i], 1); + } + + s.moveToBookmark(bm); + } + }); + + // Register plugin + tinymce.PluginManager.add('visualchars', tinymce.plugins.VisualChars); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/wordcount/editor_plugin.js b/web/js/globals/tinymce/plugins/wordcount/editor_plugin.js new file mode 100644 index 0000000..a099e6a --- /dev/null +++ b/web/js/globals/tinymce/plugins/wordcount/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.WordCount",{block:0,id:null,countre:null,cleanre:null,init:function(a,b){var c=this,d=0;c.countre=a.getParam("wordcount_countregex",/\S\s+/g);c.cleanre=a.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$¿'"_+=\\\/-]*/g);c.id=a.id+"-word-count";a.onPostRender.add(function(f,e){var g,h;h=f.getParam("wordcount_target_id");if(!h){g=tinymce.DOM.get(f.id+"_path_row");if(g){tinymce.DOM.add(g.parentNode,"div",{style:"float: right"},f.getLang("wordcount.words","Words: ")+'0')}}else{tinymce.DOM.add(h,"span",{},'0')}});a.onInit.add(function(e){e.selection.onSetContent.add(function(){c._count(e)});c._count(e)});a.onSetContent.add(function(e){c._count(e)});a.onKeyUp.add(function(f,g){if(g.keyCode==d){return}if(13==g.keyCode||8==d||46==d){c._count(f)}d=g.keyCode})},_count:function(b){var c=this,a=0;if(c.block){return}c.block=1;setTimeout(function(){var d=b.getContent({format:"raw"});if(d){d=d.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ");d=d.replace(c.cleanre,"");d.replace(c.countre,function(){a++})}tinymce.DOM.setHTML(c.id,a.toString());setTimeout(function(){c.block=0},2000)},1)},getInfo:function(){return{longname:"Word Count plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("wordcount",tinymce.plugins.WordCount)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/wordcount/editor_plugin_src.js b/web/js/globals/tinymce/plugins/wordcount/editor_plugin_src.js new file mode 100644 index 0000000..5cb92fa --- /dev/null +++ b/web/js/globals/tinymce/plugins/wordcount/editor_plugin_src.js @@ -0,0 +1,98 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.WordCount', { + block : 0, + id : null, + countre : null, + cleanre : null, + + init : function(ed, url) { + var t = this, last = 0; + + t.countre = ed.getParam('wordcount_countregex', /\S\s+/g); + t.cleanre = ed.getParam('wordcount_cleanregex', /[0-9.(),;:!?%#$¿'"_+=\\\/-]*/g); + t.id = ed.id + '-word-count'; + + ed.onPostRender.add(function(ed, cm) { + var row, id; + + // Add it to the specified id or the theme advanced path + id = ed.getParam('wordcount_target_id'); + if (!id) { + row = tinymce.DOM.get(ed.id + '_path_row'); + + if (row) + tinymce.DOM.add(row.parentNode, 'div', {'style': 'float: right'}, ed.getLang('wordcount.words', 'Words: ') + '0'); + } else + tinymce.DOM.add(id, 'span', {}, '0'); + }); + + ed.onInit.add(function(ed) { + ed.selection.onSetContent.add(function() { + t._count(ed); + }); + + t._count(ed); + }); + + ed.onSetContent.add(function(ed) { + t._count(ed); + }); + + ed.onKeyUp.add(function(ed, e) { + if (e.keyCode == last) + return; + + if (13 == e.keyCode || 8 == last || 46 == last) + t._count(ed); + + last = e.keyCode; + }); + }, + + _count : function(ed) { + var t = this, tc = 0; + + // Keep multiple calls from happening at the same time + if (t.block) + return; + + t.block = 1; + + setTimeout(function() { + var tx = ed.getContent({format : 'raw'}); + + if (tx) { + tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars + tx = tx.replace(t.cleanre, ''); // remove numbers and punctuation + tx.replace(t.countre, function() {tc++;}); // count the words + } + + tinymce.DOM.setHTML(t.id, tc.toString()); + + setTimeout(function() {t.block = 0;}, 2000); + }, 1); + }, + + getInfo: function() { + return { + longname : 'Word Count plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + tinymce.PluginManager.add('wordcount', tinymce.plugins.WordCount); +})(); diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/abbr.htm b/web/js/globals/tinymce/plugins/xhtmlxtras/abbr.htm new file mode 100644 index 0000000..3aeac0d --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/abbr.htm @@ -0,0 +1,141 @@ + + + + {#xhtmlxtras_dlg.title_abbr_element} + + + + + + + + + +
      + + +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      : + +
      :
      : + +
      : + +
      +
      +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      +
      +
      +
      +
      + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/acronym.htm b/web/js/globals/tinymce/plugins/xhtmlxtras/acronym.htm new file mode 100644 index 0000000..31ee7b7 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/acronym.htm @@ -0,0 +1,141 @@ + + + + {#xhtmlxtras_dlg.title_acronym_element} + + + + + + + + + +
      + + +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      : + +
      :
      : + +
      : + +
      +
      +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      +
      +
      +
      +
      + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/attributes.htm b/web/js/globals/tinymce/plugins/xhtmlxtras/attributes.htm new file mode 100644 index 0000000..17054da --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/attributes.htm @@ -0,0 +1,148 @@ + + + + {#xhtmlxtras_dlg.attribs_title} + + + + + + + + +
      + + +
      +
      +
      + {#xhtmlxtras_dlg.attribute_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      + +
      :
      : + +
      : + +
      +
      +
      +
      +
      + {#xhtmlxtras_dlg.attribute_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      +
      +
      +
      +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/cite.htm b/web/js/globals/tinymce/plugins/xhtmlxtras/cite.htm new file mode 100644 index 0000000..d0a3e3a --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/cite.htm @@ -0,0 +1,141 @@ + + + + {#xhtmlxtras_dlg.title_cite_element} + + + + + + + + + +
      + + +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      : + +
      :
      : + +
      : + +
      +
      +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      +
      +
      +
      +
      + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/css/attributes.css b/web/js/globals/tinymce/plugins/xhtmlxtras/css/attributes.css new file mode 100644 index 0000000..9a6a235 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/css/attributes.css @@ -0,0 +1,11 @@ +.panel_wrapper div.current { + height: 290px; +} + +#id, #style, #title, #dir, #hreflang, #lang, #classlist, #tabindex, #accesskey { + width: 200px; +} + +#events_panel input { + width: 200px; +} diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/css/popup.css b/web/js/globals/tinymce/plugins/xhtmlxtras/css/popup.css new file mode 100644 index 0000000..e67114d --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/css/popup.css @@ -0,0 +1,9 @@ +input.field, select.field {width:200px;} +input.picker {width:179px; margin-left: 5px;} +input.disabled {border-color:#F2F2F2;} +img.picker {vertical-align:text-bottom; cursor:pointer;} +h1 {padding: 0 0 5px 0;} +.panel_wrapper div.current {height:160px;} +#xhtmlxtrasdel .panel_wrapper div.current, #xhtmlxtrasins .panel_wrapper div.current {height: 230px;} +a.browse span {display:block; width:20px; height:20px; background:url('../../../themes/advanced/img/icons.gif') -140px -20px;} +#datetime {width:180px;} diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/del.htm b/web/js/globals/tinymce/plugins/xhtmlxtras/del.htm new file mode 100644 index 0000000..8b07fa8 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/del.htm @@ -0,0 +1,161 @@ + + + + {#xhtmlxtras_dlg.title_del_element} + + + + + + + + + +
      + + +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_general_tab} + + + + + + + + + +
      : + + + + + +
      +
      :
      +
      +
      + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      : + +
      :
      : + +
      : + +
      +
      +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      +
      +
      +
      +
      + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin.js b/web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin.js new file mode 100644 index 0000000..a9393ad --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.XHTMLXtrasPlugin",{init:function(a,b){a.addCommand("mceCite",function(){a.windowManager.open({file:b+"/cite.htm",width:350+parseInt(a.getLang("xhtmlxtras.cite_delta_width",0)),height:250+parseInt(a.getLang("xhtmlxtras.cite_delta_height",0)),inline:1},{plugin_url:b})});a.addCommand("mceAcronym",function(){a.windowManager.open({file:b+"/acronym.htm",width:350+parseInt(a.getLang("xhtmlxtras.acronym_delta_width",0)),height:250+parseInt(a.getLang("xhtmlxtras.acronym_delta_width",0)),inline:1},{plugin_url:b})});a.addCommand("mceAbbr",function(){a.windowManager.open({file:b+"/abbr.htm",width:350+parseInt(a.getLang("xhtmlxtras.abbr_delta_width",0)),height:250+parseInt(a.getLang("xhtmlxtras.abbr_delta_width",0)),inline:1},{plugin_url:b})});a.addCommand("mceDel",function(){a.windowManager.open({file:b+"/del.htm",width:340+parseInt(a.getLang("xhtmlxtras.del_delta_width",0)),height:310+parseInt(a.getLang("xhtmlxtras.del_delta_width",0)),inline:1},{plugin_url:b})});a.addCommand("mceIns",function(){a.windowManager.open({file:b+"/ins.htm",width:340+parseInt(a.getLang("xhtmlxtras.ins_delta_width",0)),height:310+parseInt(a.getLang("xhtmlxtras.ins_delta_width",0)),inline:1},{plugin_url:b})});a.addCommand("mceAttributes",function(){a.windowManager.open({file:b+"/attributes.htm",width:380,height:370,inline:1},{plugin_url:b})});a.addButton("cite",{title:"xhtmlxtras.cite_desc",cmd:"mceCite"});a.addButton("acronym",{title:"xhtmlxtras.acronym_desc",cmd:"mceAcronym"});a.addButton("abbr",{title:"xhtmlxtras.abbr_desc",cmd:"mceAbbr"});a.addButton("del",{title:"xhtmlxtras.del_desc",cmd:"mceDel"});a.addButton("ins",{title:"xhtmlxtras.ins_desc",cmd:"mceIns"});a.addButton("attribs",{title:"xhtmlxtras.attribs_desc",cmd:"mceAttributes"});a.onNodeChange.add(function(d,c,f,e){f=d.dom.getParent(f,"CITE,ACRONYM,ABBR,DEL,INS");c.setDisabled("cite",e);c.setDisabled("acronym",e);c.setDisabled("abbr",e);c.setDisabled("del",e);c.setDisabled("ins",e);c.setDisabled("attribs",f&&f.nodeName=="BODY");c.setActive("cite",0);c.setActive("acronym",0);c.setActive("abbr",0);c.setActive("del",0);c.setActive("ins",0);if(f){do{c.setDisabled(f.nodeName.toLowerCase(),0);c.setActive(f.nodeName.toLowerCase(),1)}while(f=f.parentNode)}});a.onPreInit.add(function(){a.dom.create("abbr")})},getInfo:function(){return{longname:"XHTML Xtras Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("xhtmlxtras",tinymce.plugins.XHTMLXtrasPlugin)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin_src.js b/web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin_src.js new file mode 100644 index 0000000..5f9d9bd --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/editor_plugin_src.js @@ -0,0 +1,132 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.XHTMLXtrasPlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceCite', function() { + ed.windowManager.open({ + file : url + '/cite.htm', + width : 350 + parseInt(ed.getLang('xhtmlxtras.cite_delta_width', 0)), + height : 250 + parseInt(ed.getLang('xhtmlxtras.cite_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceAcronym', function() { + ed.windowManager.open({ + file : url + '/acronym.htm', + width : 350 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_width', 0)), + height : 250 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_width', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceAbbr', function() { + ed.windowManager.open({ + file : url + '/abbr.htm', + width : 350 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_width', 0)), + height : 250 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_width', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceDel', function() { + ed.windowManager.open({ + file : url + '/del.htm', + width : 340 + parseInt(ed.getLang('xhtmlxtras.del_delta_width', 0)), + height : 310 + parseInt(ed.getLang('xhtmlxtras.del_delta_width', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceIns', function() { + ed.windowManager.open({ + file : url + '/ins.htm', + width : 340 + parseInt(ed.getLang('xhtmlxtras.ins_delta_width', 0)), + height : 310 + parseInt(ed.getLang('xhtmlxtras.ins_delta_width', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceAttributes', function() { + ed.windowManager.open({ + file : url + '/attributes.htm', + width : 380, + height : 370, + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register buttons + ed.addButton('cite', {title : 'xhtmlxtras.cite_desc', cmd : 'mceCite'}); + ed.addButton('acronym', {title : 'xhtmlxtras.acronym_desc', cmd : 'mceAcronym'}); + ed.addButton('abbr', {title : 'xhtmlxtras.abbr_desc', cmd : 'mceAbbr'}); + ed.addButton('del', {title : 'xhtmlxtras.del_desc', cmd : 'mceDel'}); + ed.addButton('ins', {title : 'xhtmlxtras.ins_desc', cmd : 'mceIns'}); + ed.addButton('attribs', {title : 'xhtmlxtras.attribs_desc', cmd : 'mceAttributes'}); + + ed.onNodeChange.add(function(ed, cm, n, co) { + n = ed.dom.getParent(n, 'CITE,ACRONYM,ABBR,DEL,INS'); + + cm.setDisabled('cite', co); + cm.setDisabled('acronym', co); + cm.setDisabled('abbr', co); + cm.setDisabled('del', co); + cm.setDisabled('ins', co); + cm.setDisabled('attribs', n && n.nodeName == 'BODY'); + cm.setActive('cite', 0); + cm.setActive('acronym', 0); + cm.setActive('abbr', 0); + cm.setActive('del', 0); + cm.setActive('ins', 0); + + // Activate all + if (n) { + do { + cm.setDisabled(n.nodeName.toLowerCase(), 0); + cm.setActive(n.nodeName.toLowerCase(), 1); + } while (n = n.parentNode); + } + }); + + ed.onPreInit.add(function() { + // Fixed IE issue where it can't handle these elements correctly + ed.dom.create('abbr'); + }); + }, + + getInfo : function() { + return { + longname : 'XHTML Xtras Plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('xhtmlxtras', tinymce.plugins.XHTMLXtrasPlugin); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/ins.htm b/web/js/globals/tinymce/plugins/xhtmlxtras/ins.htm new file mode 100644 index 0000000..6c5470c --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/ins.htm @@ -0,0 +1,161 @@ + + + + {#xhtmlxtras_dlg.title_ins_element} + + + + + + + + + +
      + + +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_general_tab} + + + + + + + + + +
      : + + + + + +
      +
      :
      +
      +
      + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      : + +
      :
      : + +
      : + +
      +
      +
      +
      +
      + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      :
      +
      +
      +
      +
      + + + +
      +
      + + diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/js/abbr.js b/web/js/globals/tinymce/plugins/xhtmlxtras/js/abbr.js new file mode 100644 index 0000000..4b51a25 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/js/abbr.js @@ -0,0 +1,28 @@ +/** + * abbr.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + SXE.initElementDialog('abbr'); + if (SXE.currentAction == "update") { + SXE.showRemoveButton(); + } +} + +function insertAbbr() { + SXE.insertElement('abbr'); + tinyMCEPopup.close(); +} + +function removeAbbr() { + SXE.removeElement('abbr'); + tinyMCEPopup.close(); +} + +tinyMCEPopup.onInit.add(init); diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/js/acronym.js b/web/js/globals/tinymce/plugins/xhtmlxtras/js/acronym.js new file mode 100644 index 0000000..6ec2f88 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/js/acronym.js @@ -0,0 +1,28 @@ +/** + * acronym.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + SXE.initElementDialog('acronym'); + if (SXE.currentAction == "update") { + SXE.showRemoveButton(); + } +} + +function insertAcronym() { + SXE.insertElement('acronym'); + tinyMCEPopup.close(); +} + +function removeAcronym() { + SXE.removeElement('acronym'); + tinyMCEPopup.close(); +} + +tinyMCEPopup.onInit.add(init); diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/js/attributes.js b/web/js/globals/tinymce/plugins/xhtmlxtras/js/attributes.js new file mode 100644 index 0000000..d62a219 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/js/attributes.js @@ -0,0 +1,126 @@ +/** + * attributes.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + tinyMCEPopup.resizeToInnerSize(); + var inst = tinyMCEPopup.editor; + var dom = inst.dom; + var elm = inst.selection.getNode(); + var f = document.forms[0]; + var onclick = dom.getAttrib(elm, 'onclick'); + + setFormValue('title', dom.getAttrib(elm, 'title')); + setFormValue('id', dom.getAttrib(elm, 'id')); + setFormValue('style', dom.getAttrib(elm, "style")); + setFormValue('dir', dom.getAttrib(elm, 'dir')); + setFormValue('lang', dom.getAttrib(elm, 'lang')); + setFormValue('tabindex', dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); + setFormValue('accesskey', dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); + setFormValue('onfocus', dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', onclick); + setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup')); + className = dom.getAttrib(elm, 'class'); + + addClassesToList('classlist', 'advlink_styles'); + selectByValue(f, 'classlist', className, true); + + TinyMCE_EditableSelects.init(); +} + +function setFormValue(name, value) { + if(value && document.forms[0].elements[name]){ + document.forms[0].elements[name].value = value; + } +} + +function insertAction() { + var inst = tinyMCEPopup.editor; + var elm = inst.selection.getNode(); + + tinyMCEPopup.execCommand("mceBeginUndoLevel"); + setAllAttribs(elm); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); +} + +function setAttrib(elm, attrib, value) { + var formObj = document.forms[0]; + var valueElm = formObj.elements[attrib.toLowerCase()]; + var inst = tinyMCEPopup.editor; + var dom = inst.dom; + + if (typeof(value) == "undefined" || value == null) { + value = ""; + + if (valueElm) + value = valueElm.value; + } + + if (value != "") { + dom.setAttrib(elm, attrib.toLowerCase(), value); + + if (attrib == "style") + attrib = "style.cssText"; + + if (attrib.substring(0, 2) == 'on') + value = 'return true;' + value; + + if (attrib == "class") + attrib = "className"; + + elm[attrib]=value; + } else + elm.removeAttribute(attrib); +} + +function setAllAttribs(elm) { + var f = document.forms[0]; + + setAttrib(elm, 'title'); + setAttrib(elm, 'id'); + setAttrib(elm, 'style'); + setAttrib(elm, 'class', getSelectValue(f, 'classlist')); + setAttrib(elm, 'dir'); + setAttrib(elm, 'lang'); + setAttrib(elm, 'tabindex'); + setAttrib(elm, 'accesskey'); + setAttrib(elm, 'onfocus'); + setAttrib(elm, 'onblur'); + setAttrib(elm, 'onclick'); + setAttrib(elm, 'ondblclick'); + setAttrib(elm, 'onmousedown'); + setAttrib(elm, 'onmouseup'); + setAttrib(elm, 'onmouseover'); + setAttrib(elm, 'onmousemove'); + setAttrib(elm, 'onmouseout'); + setAttrib(elm, 'onkeypress'); + setAttrib(elm, 'onkeydown'); + setAttrib(elm, 'onkeyup'); + + // Refresh in old MSIE +// if (tinyMCE.isMSIE5) +// elm.outerHTML = elm.outerHTML; +} + +function insertAttribute() { + tinyMCEPopup.close(); +} + +tinyMCEPopup.onInit.add(init); +tinyMCEPopup.requireLangPack(); diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/js/cite.js b/web/js/globals/tinymce/plugins/xhtmlxtras/js/cite.js new file mode 100644 index 0000000..009b715 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/js/cite.js @@ -0,0 +1,28 @@ +/** + * cite.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + SXE.initElementDialog('cite'); + if (SXE.currentAction == "update") { + SXE.showRemoveButton(); + } +} + +function insertCite() { + SXE.insertElement('cite'); + tinyMCEPopup.close(); +} + +function removeCite() { + SXE.removeElement('cite'); + tinyMCEPopup.close(); +} + +tinyMCEPopup.onInit.add(init); diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/js/del.js b/web/js/globals/tinymce/plugins/xhtmlxtras/js/del.js new file mode 100644 index 0000000..9e5d8c5 --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/js/del.js @@ -0,0 +1,63 @@ +/** + * del.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + SXE.initElementDialog('del'); + if (SXE.currentAction == "update") { + setFormValue('datetime', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'datetime')); + setFormValue('cite', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'cite')); + SXE.showRemoveButton(); + } +} + +function setElementAttribs(elm) { + setAllCommonAttribs(elm); + setAttrib(elm, 'datetime'); + setAttrib(elm, 'cite'); +} + +function insertDel() { + var elm = tinyMCEPopup.editor.dom.getParent(SXE.focusElement, 'DEL'); + + tinyMCEPopup.execCommand('mceBeginUndoLevel'); + if (elm == null) { + var s = SXE.inst.selection.getContent(); + if(s.length > 0) { + insertInlineElement('del'); + var elementArray = tinymce.grep(SXE.inst.dom.select('del'), function(n) {return n.id == '#sxe_temp_del#';}); + for (var i=0; i 0) { + tagName = element_name; + + insertInlineElement(element_name); + var elementArray = tinymce.grep(SXE.inst.dom.select(element_name)); + for (var i=0; i -1) ? true : false; +} + +SXE.removeClass = function(elm,cl) { + if(elm.className == null || elm.className == "" || !SXE.containsClass(elm,cl)) { + return true; + } + var classNames = elm.className.split(" "); + var newClassNames = ""; + for (var x = 0, cnl = classNames.length; x < cnl; x++) { + if (classNames[x] != cl) { + newClassNames += (classNames[x] + " "); + } + } + elm.className = newClassNames.substring(0,newClassNames.length-1); //removes extra space at the end +} + +SXE.addClass = function(elm,cl) { + if(!SXE.containsClass(elm,cl)) elm.className ? elm.className += " " + cl : elm.className = cl; + return true; +} + +function insertInlineElement(en) { + var ed = tinyMCEPopup.editor, dom = ed.dom; + + ed.getDoc().execCommand('FontName', false, 'mceinline'); + tinymce.each(dom.select('span,font'), function(n) { + if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') + dom.replace(dom.create(en, {_mce_new : 1}), n, 1); + }); +} diff --git a/web/js/globals/tinymce/plugins/xhtmlxtras/js/ins.js b/web/js/globals/tinymce/plugins/xhtmlxtras/js/ins.js new file mode 100644 index 0000000..3774f0a --- /dev/null +++ b/web/js/globals/tinymce/plugins/xhtmlxtras/js/ins.js @@ -0,0 +1,62 @@ +/** + * ins.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + SXE.initElementDialog('ins'); + if (SXE.currentAction == "update") { + setFormValue('datetime', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'datetime')); + setFormValue('cite', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'cite')); + SXE.showRemoveButton(); + } +} + +function setElementAttribs(elm) { + setAllCommonAttribs(elm); + setAttrib(elm, 'datetime'); + setAttrib(elm, 'cite'); +} + +function insertIns() { + var elm = tinyMCEPopup.editor.dom.getParent(SXE.focusElement, 'INS'); + tinyMCEPopup.execCommand('mceBeginUndoLevel'); + if (elm == null) { + var s = SXE.inst.selection.getContent(); + if(s.length > 0) { + insertInlineElement('INS'); + var elementArray = tinymce.grep(SXE.inst.dom.select('ins'), function(n) {return n.id == '#sxe_temp_ins#';}); + for (var i=0; i + + + {#advanced_dlg.about_title} + + + + + + + +
      +
      +

      {#advanced_dlg.about_title}

      +

      Version: ()

      +

      TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor control released as Open Source under LGPL + by Moxiecode Systems AB. It has the ability to convert HTML TEXTAREA fields or other HTML elements to editor instances.

      +

      Copyright © 2003-2008, Moxiecode Systems AB, All rights reserved.

      +

      For more information about this software visit the TinyMCE website.

      + +
      + Got Moxie? + Hosted By Sourceforge + Also on freshmeat +
      +
      + +
      +
      +

      {#advanced_dlg.about_loaded}

      + +
      +
      + +

       

      +
      +
      + +
      +
      +
      +
      + +
      + +
      + + diff --git a/web/js/globals/tinymce/themes/advanced/anchor.htm b/web/js/globals/tinymce/themes/advanced/anchor.htm new file mode 100644 index 0000000..2bc63fc --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/anchor.htm @@ -0,0 +1,26 @@ + + + + {#advanced_dlg.anchor_title} + + + + +
      + + + + + + + + +
      {#advanced_dlg.anchor_title}
      {#advanced_dlg.anchor_name}:
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/themes/advanced/charmap.htm b/web/js/globals/tinymce/themes/advanced/charmap.htm new file mode 100644 index 0000000..3991b81 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/charmap.htm @@ -0,0 +1,52 @@ + + + + {#advanced_dlg.charmap_title} + + + + + + + + + + + + + + + +
      {#advanced_dlg.charmap_title}
      + + + + + + + + + +
       
       
      +
      + + + + + + + + + + + + + + + + +
      HTML-Code
       
       
      NUM-Code
       
      +
      + + + diff --git a/web/js/globals/tinymce/themes/advanced/color_picker.htm b/web/js/globals/tinymce/themes/advanced/color_picker.htm new file mode 100644 index 0000000..096e755 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/color_picker.htm @@ -0,0 +1,73 @@ + + + + {#advanced_dlg.colorpicker_title} + + + + + +
      + + +
      +
      +
      + {#advanced_dlg.colorpicker_picker_title} +
      + + +
      + +
      + +
      +
      +
      +
      + +
      +
      + {#advanced_dlg.colorpicker_palette_title} +
      + +
      + +
      +
      +
      + +
      +
      + {#advanced_dlg.colorpicker_named_title} +
      + +
      + +
      + +
      + {#advanced_dlg.colorpicker_name} +
      +
      +
      +
      + +
      + + +
      + +
      + +
      +
      +
      + + diff --git a/web/js/globals/tinymce/themes/advanced/editor_template.js b/web/js/globals/tinymce/themes/advanced/editor_template.js new file mode 100644 index 0000000..c5a1719 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/editor_template.js @@ -0,0 +1 @@ +(function(e){var d=e.DOM,b=e.dom.Event,h=e.extend,f=e.each,a=e.util.Cookie,g,c=e.explode;e.ThemeManager.requireLangPack("advanced");e.create("tinymce.themes.AdvancedTheme",{sizes:[8,10,12,14,18,24,36],controls:{bold:["bold_desc","Bold"],italic:["italic_desc","Italic"],underline:["underline_desc","Underline"],strikethrough:["striketrough_desc","Strikethrough"],justifyleft:["justifyleft_desc","JustifyLeft"],justifycenter:["justifycenter_desc","JustifyCenter"],justifyright:["justifyright_desc","JustifyRight"],justifyfull:["justifyfull_desc","JustifyFull"],bullist:["bullist_desc","InsertUnorderedList"],numlist:["numlist_desc","InsertOrderedList"],outdent:["outdent_desc","Outdent"],indent:["indent_desc","Indent"],cut:["cut_desc","Cut"],copy:["copy_desc","Copy"],paste:["paste_desc","Paste"],undo:["undo_desc","Undo"],redo:["redo_desc","Redo"],link:["link_desc","mceLink"],unlink:["unlink_desc","unlink"],image:["image_desc","mceImage"],cleanup:["cleanup_desc","mceCleanup"],help:["help_desc","mceHelp"],code:["code_desc","mceCodeEditor"],hr:["hr_desc","InsertHorizontalRule"],removeformat:["removeformat_desc","RemoveFormat"],sub:["sub_desc","subscript"],sup:["sup_desc","superscript"],forecolor:["forecolor_desc","ForeColor"],forecolorpicker:["forecolor_desc","mceForeColor"],backcolor:["backcolor_desc","HiliteColor"],backcolorpicker:["backcolor_desc","mceBackColor"],charmap:["charmap_desc","mceCharMap"],visualaid:["visualaid_desc","mceToggleVisualAid"],anchor:["anchor_desc","mceInsertAnchor"],newdocument:["newdocument_desc","mceNewDocument"],blockquote:["blockquote_desc","mceBlockQuote"]},stateControls:["bold","italic","underline","strikethrough","bullist","numlist","justifyleft","justifycenter","justifyright","justifyfull","sub","sup","blockquote"],init:function(j,k){var l=this,m,i,n;l.editor=j;l.url=k;l.onResolveName=new e.util.Dispatcher(this);l.settings=m=h({theme_advanced_path:true,theme_advanced_toolbar_location:"bottom",theme_advanced_buttons1:"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect",theme_advanced_buttons2:"bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code",theme_advanced_buttons3:"hr,removeformat,visualaid,|,sub,sup,|,charmap",theme_advanced_blockformats:"p,address,pre,h1,h2,h3,h4,h5,h6",theme_advanced_toolbar_align:"center",theme_advanced_fonts:"Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",theme_advanced_more_colors:1,theme_advanced_row_height:23,theme_advanced_resize_horizontal:1,theme_advanced_resizing_use_cookie:1,theme_advanced_font_sizes:"1,2,3,4,5,6,7",readonly:j.settings.readonly},j.settings);if(!m.font_size_style_values){m.font_size_style_values="8pt,10pt,12pt,14pt,18pt,24pt,36pt"}if(e.is(m.theme_advanced_font_sizes,"string")){m.font_size_style_values=e.explode(m.font_size_style_values);m.font_size_classes=e.explode(m.font_size_classes||"");n={};j.settings.theme_advanced_font_sizes=m.theme_advanced_font_sizes;f(j.getParam("theme_advanced_font_sizes","","hash"),function(q,p){var o;if(p==q&&q>=1&&q<=7){p=q+" ("+l.sizes[q-1]+"pt)";o=m.font_size_classes[q-1];q=m.font_size_style_values[q-1]||(l.sizes[q-1]+"pt")}if(/^\s*\./.test(q)){o=q.replace(/\./g,"")}n[p]=o?{"class":o}:{fontSize:q}});m.theme_advanced_font_sizes=n}if((i=m.theme_advanced_path_location)&&i!="none"){m.theme_advanced_statusbar_location=m.theme_advanced_path_location}if(m.theme_advanced_statusbar_location=="none"){m.theme_advanced_statusbar_location=0}j.onInit.add(function(){if(!j.settings.readonly){j.onNodeChange.add(l._nodeChanged,l)}if(j.settings.content_css!==false){j.dom.loadCSS(j.baseURI.toAbsolute(k+"/skins/"+j.settings.skin+"/content.css"))}});j.onSetProgressState.add(function(q,o,r){var s,t=q.id,p;if(o){l.progressTimer=setTimeout(function(){s=q.getContainer();s=s.insertBefore(d.create("DIV",{style:"position:relative"}),s.firstChild);p=d.get(q.id+"_tbl");d.add(s,"div",{id:t+"_blocker","class":"mceBlocker",style:{width:p.clientWidth+2,height:p.clientHeight+2}});d.add(s,"div",{id:t+"_progress","class":"mceProgress",style:{left:p.clientWidth/2,top:p.clientHeight/2}})},r||0)}else{d.remove(t+"_blocker");d.remove(t+"_progress");clearTimeout(l.progressTimer)}});d.loadCSS(m.editor_css?j.documentBaseURI.toAbsolute(m.editor_css):k+"/skins/"+j.settings.skin+"/ui.css");if(m.skin_variant){d.loadCSS(k+"/skins/"+j.settings.skin+"/ui_"+m.skin_variant+".css")}},createControl:function(l,i){var j,k;if(k=i.createControl(l)){return k}switch(l){case"styleselect":return this._createStyleSelect();case"formatselect":return this._createBlockFormats();case"fontselect":return this._createFontSelect();case"fontsizeselect":return this._createFontSizeSelect();case"forecolor":return this._createForeColorMenu();case"backcolor":return this._createBackColorMenu()}if((j=this.controls[l])){return i.createButton(l,{title:"advanced."+j[0],cmd:j[1],ui:j[2],value:j[3]})}},execCommand:function(k,j,l){var i=this["_"+k];if(i){i.call(this,j,l);return true}return false},_importClasses:function(k){var i=this.editor,j=i.controlManager.get("styleselect");if(j.getLength()==0){f(i.dom.getClasses(),function(n,l){var m="style_"+l;i.formatter.register(m,{inline:"span",attributes:{"class":n["class"]},selector:"*"});j.add(n["class"],m)})}},_createStyleSelect:function(m){var k=this,i=k.editor,j=i.controlManager,l;l=j.createListBox("styleselect",{title:"advanced.style_select",onselect:function(o){var p,n=[];f(l.items,function(q){n.push(q.value)});i.focus();i.undoManager.add();p=i.formatter.matchAll(n);if(!o||p[0]==o){i.formatter.remove(p[0])}else{i.formatter.apply(o)}i.undoManager.add();i.nodeChanged();return false}});i.onInit.add(function(){var o=0,n=i.getParam("style_formats");if(n){f(n,function(p){var q,r=0;f(p,function(){r++});if(r>1){q=p.name=p.name||"style_"+(o++);i.formatter.register(q,p);l.add(p.title,q)}else{l.add(p.title)}})}else{f(i.getParam("theme_advanced_styles","","hash"),function(r,q){var p;if(r){p="style_"+(o++);i.formatter.register(p,{inline:"span",classes:r,selector:"*"});l.add(k.editor.translate(q),p)}})}});if(l.getLength()==0){l.onPostRender.add(function(o,p){if(!l.NativeListBox){b.add(p.id+"_text","focus",k._importClasses,k);b.add(p.id+"_text","mousedown",k._importClasses,k);b.add(p.id+"_open","focus",k._importClasses,k);b.add(p.id+"_open","mousedown",k._importClasses,k)}else{b.add(p.id,"focus",k._importClasses,k)}})}return l},_createFontSelect:function(){var k,j=this,i=j.editor;k=i.controlManager.createListBox("fontselect",{title:"advanced.fontdefault",onselect:function(l){var m=k.items[k.selectedIndex];if(!l&&m){i.execCommand("FontName",false,m.value);return}i.execCommand("FontName",false,l);k.select(function(n){return l==n});return false}});if(k){f(i.getParam("theme_advanced_fonts",j.settings.theme_advanced_fonts,"hash"),function(m,l){k.add(i.translate(l),m,{style:m.indexOf("dings")==-1?"font-family:"+m:""})})}return k},_createFontSizeSelect:function(){var m=this,k=m.editor,n,l=0,j=[];n=k.controlManager.createListBox("fontsizeselect",{title:"advanced.font_size",onselect:function(i){var o=n.items[n.selectedIndex];if(!i&&o){o=o.value;if(o["class"]){k.formatter.toggle("fontsize_class",{value:o["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,o.fontSize)}return}if(i["class"]){k.focus();k.undoManager.add();k.formatter.toggle("fontsize_class",{value:i["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,i.fontSize)}n.select(function(p){return i==p});return false}});if(n){f(m.settings.theme_advanced_font_sizes,function(o,i){var p=o.fontSize;if(p>=1&&p<=7){p=m.sizes[parseInt(p)-1]+"pt"}n.add(i,o,{style:"font-size:"+p,"class":"mceFontSize"+(l++)+(" "+(o["class"]||""))})})}return n},_createBlockFormats:function(){var k,i={p:"advanced.paragraph",address:"advanced.address",pre:"advanced.pre",h1:"advanced.h1",h2:"advanced.h2",h3:"advanced.h3",h4:"advanced.h4",h5:"advanced.h5",h6:"advanced.h6",div:"advanced.div",blockquote:"advanced.blockquote",code:"advanced.code",dt:"advanced.dt",dd:"advanced.dd",samp:"advanced.samp"},j=this;k=j.editor.controlManager.createListBox("formatselect",{title:"advanced.block",cmd:"FormatBlock"});if(k){f(j.editor.getParam("theme_advanced_blockformats",j.settings.theme_advanced_blockformats,"hash"),function(m,l){k.add(j.editor.translate(l!=m?l:i[m]),m,{"class":"mce_formatPreview mce_"+m})})}return k},_createForeColorMenu:function(){var m,j=this,k=j.settings,l={},i;if(k.theme_advanced_more_colors){l.more_colors_func=function(){j._mceColorPicker(0,{color:m.value,func:function(n){m.setColor(n)}})}}if(i=k.theme_advanced_text_colors){l.colors=i}if(k.theme_advanced_default_foreground_color){l.default_color=k.theme_advanced_default_foreground_color}l.title="advanced.forecolor_desc";l.cmd="ForeColor";l.scope=this;m=j.editor.controlManager.createColorSplitButton("forecolor",l);return m},_createBackColorMenu:function(){var m,j=this,k=j.settings,l={},i;if(k.theme_advanced_more_colors){l.more_colors_func=function(){j._mceColorPicker(0,{color:m.value,func:function(n){m.setColor(n)}})}}if(i=k.theme_advanced_background_colors){l.colors=i}if(k.theme_advanced_default_background_color){l.default_color=k.theme_advanced_default_background_color}l.title="advanced.backcolor_desc";l.cmd="HiliteColor";l.scope=this;m=j.editor.controlManager.createColorSplitButton("backcolor",l);return m},renderUI:function(k){var m,l,q,v=this,r=v.editor,w=v.settings,u,j,i;m=j=d.create("span",{id:r.id+"_parent","class":"mceEditor "+r.settings.skin+"Skin"+(w.skin_variant?" "+r.settings.skin+"Skin"+v._ufirst(w.skin_variant):"")});if(!d.boxModel){m=d.add(m,"div",{"class":"mceOldBoxModel"})}m=u=d.add(m,"table",{id:r.id+"_tbl","class":"mceLayout",cellSpacing:0,cellPadding:0});m=q=d.add(m,"tbody");switch((w.theme_advanced_layout_manager||"").toLowerCase()){case"rowlayout":l=v._rowLayout(w,q,k);break;case"customlayout":l=r.execCallback("theme_advanced_custom_layout",w,q,k,j);break;default:l=v._simpleLayout(w,q,k,j)}m=k.targetNode;i=d.stdMode?u.getElementsByTagName("tr"):u.rows;d.addClass(i[0],"mceFirst");d.addClass(i[i.length-1],"mceLast");f(d.select("tr",q),function(o){d.addClass(o.firstChild,"mceFirst");d.addClass(o.childNodes[o.childNodes.length-1],"mceLast")});if(d.get(w.theme_advanced_toolbar_container)){d.get(w.theme_advanced_toolbar_container).appendChild(j)}else{d.insertAfter(j,m)}b.add(r.id+"_path_row","click",function(n){n=n.target;if(n.nodeName=="A"){v._sel(n.className.replace(/^.*mcePath_([0-9]+).*$/,"$1"));return b.cancel(n)}});if(!r.getParam("accessibility_focus")){b.add(d.add(j,"a",{href:"#"},""),"focus",function(){tinyMCE.get(r.id).focus()})}if(w.theme_advanced_toolbar_location=="external"){k.deltaHeight=0}v.deltaHeight=k.deltaHeight;k.targetNode=null;return{iframeContainer:l,editorContainer:r.id+"_parent",sizeContainer:u,deltaHeight:k.deltaHeight}},getInfo:function(){return{longname:"Advanced theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:e.majorVersion+"."+e.minorVersion}},resizeBy:function(i,j){var k=d.get(this.editor.id+"_tbl");this.resizeTo(k.clientWidth+i,k.clientHeight+j)},resizeTo:function(i,m,k){var j=this.editor,l=this.settings,n=d.get(j.id+"_tbl"),o=d.get(j.id+"_ifr");i=Math.max(l.theme_advanced_resizing_min_width||100,i);m=Math.max(l.theme_advanced_resizing_min_height||100,m);i=Math.min(l.theme_advanced_resizing_max_width||65535,i);m=Math.min(l.theme_advanced_resizing_max_height||65535,m);d.setStyle(n,"height","");d.setStyle(o,"height",m);if(l.theme_advanced_resize_horizontal){d.setStyle(n,"width","");d.setStyle(o,"width",i);if(i"))}q.push(d.createHTML("a",{href:"#",accesskey:"q",title:r.getLang("advanced.toolbar_focus")},""));for(p=1;(y=A["theme_advanced_buttons"+p]);p++){m=j.createToolbar("toolbar"+p,{"class":"mceToolbarRow"+p});if(A["theme_advanced_buttons"+p+"_add"]){y+=","+A["theme_advanced_buttons"+p+"_add"]}if(A["theme_advanced_buttons"+p+"_add_before"]){y=A["theme_advanced_buttons"+p+"_add_before"]+","+y}z._addControls(y,m);q.push(m.renderHTML());k.deltaHeight-=A.theme_advanced_row_height}q.push(d.createHTML("a",{href:"#",accesskey:"z",title:r.getLang("advanced.toolbar_focus"),onfocus:"tinyMCE.getInstanceById('"+r.id+"').focus();"},""));d.setHTML(l,q.join(""))},_addStatusBar:function(m,j){var k,v=this,p=v.editor,w=v.settings,i,q,u,l;k=d.add(m,"tr");k=l=d.add(k,"td",{"class":"mceStatusbar"});k=d.add(k,"div",{id:p.id+"_path_row"},w.theme_advanced_path?p.translate("advanced.path")+": ":" ");d.add(k,"a",{href:"#",accesskey:"x"});if(w.theme_advanced_resizing){d.add(l,"a",{id:p.id+"_resize",href:"javascript:;",onclick:"return false;","class":"mceResize"});if(w.theme_advanced_resizing_use_cookie){p.onPostRender.add(function(){var n=a.getHash("TinyMCE_"+p.id+"_size"),r=d.get(p.id+"_tbl");if(!n){return}v.resizeTo(n.cw,n.ch)})}p.onPostRender.add(function(){b.add(p.id+"_resize","click",function(n){n.preventDefault()});b.add(p.id+"_resize","mousedown",function(D){var t,r,s,o,C,z,A,F,n,E,x;function y(G){G.preventDefault();n=A+(G.screenX-C);E=F+(G.screenY-z);v.resizeTo(n,E)}function B(G){b.remove(d.doc,"mousemove",t);b.remove(p.getDoc(),"mousemove",r);b.remove(d.doc,"mouseup",s);b.remove(p.getDoc(),"mouseup",o);n=A+(G.screenX-C);E=F+(G.screenY-z);v.resizeTo(n,E,true)}D.preventDefault();C=D.screenX;z=D.screenY;x=d.get(v.editor.id+"_ifr");A=n=x.clientWidth;F=E=x.clientHeight;t=b.add(d.doc,"mousemove",y);r=b.add(p.getDoc(),"mousemove",y);s=b.add(d.doc,"mouseup",B);o=b.add(p.getDoc(),"mouseup",B)})})}j.deltaHeight-=21;k=m=null},_nodeChanged:function(r,z,l,x,j){var C=this,i,y=0,B,u,D=C.settings,A,k,w,m,q;e.each(C.stateControls,function(n){z.setActive(n,r.queryCommandState(C.controls[n][1]))});function o(p){var s,n=j.parents,t=p;if(typeof(p)=="string"){t=function(v){return v.nodeName==p}}for(s=0;s= 1 && v <= 7) { + k = v + ' (' + t.sizes[v - 1] + 'pt)'; + cl = s.font_size_classes[v - 1]; + v = s.font_size_style_values[v - 1] || (t.sizes[v - 1] + 'pt'); + } + + if (/^\s*\./.test(v)) + cl = v.replace(/\./g, ''); + + o[k] = cl ? {'class' : cl} : {fontSize : v}; + }); + + s.theme_advanced_font_sizes = o; + } + + if ((v = s.theme_advanced_path_location) && v != 'none') + s.theme_advanced_statusbar_location = s.theme_advanced_path_location; + + if (s.theme_advanced_statusbar_location == 'none') + s.theme_advanced_statusbar_location = 0; + + // Init editor + ed.onInit.add(function() { + if (!ed.settings.readonly) + ed.onNodeChange.add(t._nodeChanged, t); + + if (ed.settings.content_css !== false) + ed.dom.loadCSS(ed.baseURI.toAbsolute(url + "/skins/" + ed.settings.skin + "/content.css")); + }); + + ed.onSetProgressState.add(function(ed, b, ti) { + var co, id = ed.id, tb; + + if (b) { + t.progressTimer = setTimeout(function() { + co = ed.getContainer(); + co = co.insertBefore(DOM.create('DIV', {style : 'position:relative'}), co.firstChild); + tb = DOM.get(ed.id + '_tbl'); + + DOM.add(co, 'div', {id : id + '_blocker', 'class' : 'mceBlocker', style : {width : tb.clientWidth + 2, height : tb.clientHeight + 2}}); + DOM.add(co, 'div', {id : id + '_progress', 'class' : 'mceProgress', style : {left : tb.clientWidth / 2, top : tb.clientHeight / 2}}); + }, ti || 0); + } else { + DOM.remove(id + '_blocker'); + DOM.remove(id + '_progress'); + clearTimeout(t.progressTimer); + } + }); + + DOM.loadCSS(s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : url + "/skins/" + ed.settings.skin + "/ui.css"); + + if (s.skin_variant) + DOM.loadCSS(url + "/skins/" + ed.settings.skin + "/ui_" + s.skin_variant + ".css"); + }, + + createControl : function(n, cf) { + var cd, c; + + if (c = cf.createControl(n)) + return c; + + switch (n) { + case "styleselect": + return this._createStyleSelect(); + + case "formatselect": + return this._createBlockFormats(); + + case "fontselect": + return this._createFontSelect(); + + case "fontsizeselect": + return this._createFontSizeSelect(); + + case "forecolor": + return this._createForeColorMenu(); + + case "backcolor": + return this._createBackColorMenu(); + } + + if ((cd = this.controls[n])) + return cf.createButton(n, {title : "advanced." + cd[0], cmd : cd[1], ui : cd[2], value : cd[3]}); + }, + + execCommand : function(cmd, ui, val) { + var f = this['_' + cmd]; + + if (f) { + f.call(this, ui, val); + return true; + } + + return false; + }, + + _importClasses : function(e) { + var ed = this.editor, ctrl = ed.controlManager.get('styleselect'); + + if (ctrl.getLength() == 0) { + each(ed.dom.getClasses(), function(o, idx) { + var name = 'style_' + idx; + + ed.formatter.register(name, { + inline : 'span', + attributes : {'class' : o['class']}, + selector : '*' + }); + + ctrl.add(o['class'], name); + }); + } + }, + + _createStyleSelect : function(n) { + var t = this, ed = t.editor, ctrlMan = ed.controlManager, ctrl; + + // Setup style select box + ctrl = ctrlMan.createListBox('styleselect', { + title : 'advanced.style_select', + onselect : function(name) { + var matches, formatNames = []; + + each(ctrl.items, function(item) { + formatNames.push(item.value); + }); + + ed.focus(); + ed.undoManager.add(); + + // Toggle off the current format + matches = ed.formatter.matchAll(formatNames); + if (!name || matches[0] == name) + ed.formatter.remove(matches[0]); + else + ed.formatter.apply(name); + + ed.undoManager.add(); + ed.nodeChanged(); + + return false; // No auto select + } + }); + + // Handle specified format + ed.onInit.add(function() { + var counter = 0, formats = ed.getParam('style_formats'); + + if (formats) { + each(formats, function(fmt) { + var name, keys = 0; + + each(fmt, function() {keys++;}); + + if (keys > 1) { + name = fmt.name = fmt.name || 'style_' + (counter++); + ed.formatter.register(name, fmt); + ctrl.add(fmt.title, name); + } else + ctrl.add(fmt.title); + }); + } else { + each(ed.getParam('theme_advanced_styles', '', 'hash'), function(val, key) { + var name; + + if (val) { + name = 'style_' + (counter++); + + ed.formatter.register(name, { + inline : 'span', + classes : val, + selector : '*' + }); + + ctrl.add(t.editor.translate(key), name); + } + }); + } + }); + + // Auto import classes if the ctrl box is empty + if (ctrl.getLength() == 0) { + ctrl.onPostRender.add(function(ed, n) { + if (!ctrl.NativeListBox) { + Event.add(n.id + '_text', 'focus', t._importClasses, t); + Event.add(n.id + '_text', 'mousedown', t._importClasses, t); + Event.add(n.id + '_open', 'focus', t._importClasses, t); + Event.add(n.id + '_open', 'mousedown', t._importClasses, t); + } else + Event.add(n.id, 'focus', t._importClasses, t); + }); + } + + return ctrl; + }, + + _createFontSelect : function() { + var c, t = this, ed = t.editor; + + c = ed.controlManager.createListBox('fontselect', { + title : 'advanced.fontdefault', + onselect : function(v) { + var cur = c.items[c.selectedIndex]; + + if (!v && cur) { + ed.execCommand('FontName', false, cur.value); + return; + } + + ed.execCommand('FontName', false, v); + + // Fake selection, execCommand will fire a nodeChange and update the selection + c.select(function(sv) { + return v == sv; + }); + + return false; // No auto select + } + }); + + if (c) { + each(ed.getParam('theme_advanced_fonts', t.settings.theme_advanced_fonts, 'hash'), function(v, k) { + c.add(ed.translate(k), v, {style : v.indexOf('dings') == -1 ? 'font-family:' + v : ''}); + }); + } + + return c; + }, + + _createFontSizeSelect : function() { + var t = this, ed = t.editor, c, i = 0, cl = []; + + c = ed.controlManager.createListBox('fontsizeselect', {title : 'advanced.font_size', onselect : function(v) { + var cur = c.items[c.selectedIndex]; + + if (!v && cur) { + cur = cur.value; + + if (cur['class']) { + ed.formatter.toggle('fontsize_class', {value : cur['class']}); + ed.undoManager.add(); + ed.nodeChanged(); + } else { + ed.execCommand('FontSize', false, cur.fontSize); + } + + return; + } + + if (v['class']) { + ed.focus(); + ed.undoManager.add(); + ed.formatter.toggle('fontsize_class', {value : v['class']}); + ed.undoManager.add(); + ed.nodeChanged(); + } else + ed.execCommand('FontSize', false, v.fontSize); + + // Fake selection, execCommand will fire a nodeChange and update the selection + c.select(function(sv) { + return v == sv; + }); + + return false; // No auto select + }}); + + if (c) { + each(t.settings.theme_advanced_font_sizes, function(v, k) { + var fz = v.fontSize; + + if (fz >= 1 && fz <= 7) + fz = t.sizes[parseInt(fz) - 1] + 'pt'; + + c.add(k, v, {'style' : 'font-size:' + fz, 'class' : 'mceFontSize' + (i++) + (' ' + (v['class'] || ''))}); + }); + } + + return c; + }, + + _createBlockFormats : function() { + var c, fmts = { + p : 'advanced.paragraph', + address : 'advanced.address', + pre : 'advanced.pre', + h1 : 'advanced.h1', + h2 : 'advanced.h2', + h3 : 'advanced.h3', + h4 : 'advanced.h4', + h5 : 'advanced.h5', + h6 : 'advanced.h6', + div : 'advanced.div', + blockquote : 'advanced.blockquote', + code : 'advanced.code', + dt : 'advanced.dt', + dd : 'advanced.dd', + samp : 'advanced.samp' + }, t = this; + + c = t.editor.controlManager.createListBox('formatselect', {title : 'advanced.block', cmd : 'FormatBlock'}); + if (c) { + each(t.editor.getParam('theme_advanced_blockformats', t.settings.theme_advanced_blockformats, 'hash'), function(v, k) { + c.add(t.editor.translate(k != v ? k : fmts[v]), v, {'class' : 'mce_formatPreview mce_' + v}); + }); + } + + return c; + }, + + _createForeColorMenu : function() { + var c, t = this, s = t.settings, o = {}, v; + + if (s.theme_advanced_more_colors) { + o.more_colors_func = function() { + t._mceColorPicker(0, { + color : c.value, + func : function(co) { + c.setColor(co); + } + }); + }; + } + + if (v = s.theme_advanced_text_colors) + o.colors = v; + + if (s.theme_advanced_default_foreground_color) + o.default_color = s.theme_advanced_default_foreground_color; + + o.title = 'advanced.forecolor_desc'; + o.cmd = 'ForeColor'; + o.scope = this; + + c = t.editor.controlManager.createColorSplitButton('forecolor', o); + + return c; + }, + + _createBackColorMenu : function() { + var c, t = this, s = t.settings, o = {}, v; + + if (s.theme_advanced_more_colors) { + o.more_colors_func = function() { + t._mceColorPicker(0, { + color : c.value, + func : function(co) { + c.setColor(co); + } + }); + }; + } + + if (v = s.theme_advanced_background_colors) + o.colors = v; + + if (s.theme_advanced_default_background_color) + o.default_color = s.theme_advanced_default_background_color; + + o.title = 'advanced.backcolor_desc'; + o.cmd = 'HiliteColor'; + o.scope = this; + + c = t.editor.controlManager.createColorSplitButton('backcolor', o); + + return c; + }, + + renderUI : function(o) { + var n, ic, tb, t = this, ed = t.editor, s = t.settings, sc, p, nl; + + n = p = DOM.create('span', {id : ed.id + '_parent', 'class' : 'mceEditor ' + ed.settings.skin + 'Skin' + (s.skin_variant ? ' ' + ed.settings.skin + 'Skin' + t._ufirst(s.skin_variant) : '')}); + + if (!DOM.boxModel) + n = DOM.add(n, 'div', {'class' : 'mceOldBoxModel'}); + + n = sc = DOM.add(n, 'table', {id : ed.id + '_tbl', 'class' : 'mceLayout', cellSpacing : 0, cellPadding : 0}); + n = tb = DOM.add(n, 'tbody'); + + switch ((s.theme_advanced_layout_manager || '').toLowerCase()) { + case "rowlayout": + ic = t._rowLayout(s, tb, o); + break; + + case "customlayout": + ic = ed.execCallback("theme_advanced_custom_layout", s, tb, o, p); + break; + + default: + ic = t._simpleLayout(s, tb, o, p); + } + + n = o.targetNode; + + // Add classes to first and last TRs + nl = DOM.stdMode ? sc.getElementsByTagName('tr') : sc.rows; // Quick fix for IE 8 + DOM.addClass(nl[0], 'mceFirst'); + DOM.addClass(nl[nl.length - 1], 'mceLast'); + + // Add classes to first and last TDs + each(DOM.select('tr', tb), function(n) { + DOM.addClass(n.firstChild, 'mceFirst'); + DOM.addClass(n.childNodes[n.childNodes.length - 1], 'mceLast'); + }); + + if (DOM.get(s.theme_advanced_toolbar_container)) + DOM.get(s.theme_advanced_toolbar_container).appendChild(p); + else + DOM.insertAfter(p, n); + + Event.add(ed.id + '_path_row', 'click', function(e) { + e = e.target; + + if (e.nodeName == 'A') { + t._sel(e.className.replace(/^.*mcePath_([0-9]+).*$/, '$1')); + + return Event.cancel(e); + } + }); +/* + if (DOM.get(ed.id + '_path_row')) { + Event.add(ed.id + '_tbl', 'mouseover', function(e) { + var re; + + e = e.target; + + if (e.nodeName == 'SPAN' && DOM.hasClass(e.parentNode, 'mceButton')) { + re = DOM.get(ed.id + '_path_row'); + t.lastPath = re.innerHTML; + DOM.setHTML(re, e.parentNode.title); + } + }); + + Event.add(ed.id + '_tbl', 'mouseout', function(e) { + if (t.lastPath) { + DOM.setHTML(ed.id + '_path_row', t.lastPath); + t.lastPath = 0; + } + }); + } +*/ + + if (!ed.getParam('accessibility_focus')) + Event.add(DOM.add(p, 'a', {href : '#'}, ''), 'focus', function() {tinyMCE.get(ed.id).focus();}); + + if (s.theme_advanced_toolbar_location == 'external') + o.deltaHeight = 0; + + t.deltaHeight = o.deltaHeight; + o.targetNode = null; + + return { + iframeContainer : ic, + editorContainer : ed.id + '_parent', + sizeContainer : sc, + deltaHeight : o.deltaHeight + }; + }, + + getInfo : function() { + return { + longname : 'Advanced theme', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + version : tinymce.majorVersion + "." + tinymce.minorVersion + } + }, + + resizeBy : function(dw, dh) { + var e = DOM.get(this.editor.id + '_tbl'); + + this.resizeTo(e.clientWidth + dw, e.clientHeight + dh); + }, + + resizeTo : function(w, h, store) { + var ed = this.editor, s = this.settings, e = DOM.get(ed.id + '_tbl'), ifr = DOM.get(ed.id + '_ifr'); + + // Boundery fix box + w = Math.max(s.theme_advanced_resizing_min_width || 100, w); + h = Math.max(s.theme_advanced_resizing_min_height || 100, h); + w = Math.min(s.theme_advanced_resizing_max_width || 0xFFFF, w); + h = Math.min(s.theme_advanced_resizing_max_height || 0xFFFF, h); + + // Resize iframe and container + DOM.setStyle(e, 'height', ''); + DOM.setStyle(ifr, 'height', h); + + if (s.theme_advanced_resize_horizontal) { + DOM.setStyle(e, 'width', ''); + DOM.setStyle(ifr, 'width', w); + + // Make sure that the size is never smaller than the over all ui + if (w < e.clientWidth) { + w = e.clientWidth; + DOM.setStyle(ifr, 'width', e.clientWidth); + } + } + + // Store away the size + if (store && s.theme_advanced_resizing_use_cookie) { + Cookie.setHash("TinyMCE_" + ed.id + "_size", { + cw : w, + ch : h + }); + } + }, + + destroy : function() { + var id = this.editor.id; + + Event.clear(id + '_resize'); + Event.clear(id + '_path_row'); + Event.clear(id + '_external_close'); + }, + + // Internal functions + + _simpleLayout : function(s, tb, o, p) { + var t = this, ed = t.editor, lo = s.theme_advanced_toolbar_location, sl = s.theme_advanced_statusbar_location, n, ic, etb, c; + + if (s.readonly) { + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + return ic; + } + + // Create toolbar container at top + if (lo == 'top') + t._addToolbars(tb, o); + + // Create external toolbar + if (lo == 'external') { + n = c = DOM.create('div', {style : 'position:relative'}); + n = DOM.add(n, 'div', {id : ed.id + '_external', 'class' : 'mceExternalToolbar'}); + DOM.add(n, 'a', {id : ed.id + '_external_close', href : 'javascript:;', 'class' : 'mceExternalClose'}); + n = DOM.add(n, 'table', {id : ed.id + '_tblext', cellSpacing : 0, cellPadding : 0}); + etb = DOM.add(n, 'tbody'); + + if (p.firstChild.className == 'mceOldBoxModel') + p.firstChild.appendChild(c); + else + p.insertBefore(c, p.firstChild); + + t._addToolbars(etb, o); + + ed.onMouseUp.add(function() { + var e = DOM.get(ed.id + '_external'); + DOM.show(e); + + DOM.hide(lastExtID); + + var f = Event.add(ed.id + '_external_close', 'click', function() { + DOM.hide(ed.id + '_external'); + Event.remove(ed.id + '_external_close', 'click', f); + }); + + DOM.show(e); + DOM.setStyle(e, 'top', 0 - DOM.getRect(ed.id + '_tblext').h - 1); + + // Fixes IE rendering bug + DOM.hide(e); + DOM.show(e); + e.style.filter = ''; + + lastExtID = ed.id + '_external'; + + e = null; + }); + } + + if (sl == 'top') + t._addStatusBar(tb, o); + + // Create iframe container + if (!s.theme_advanced_toolbar_container) { + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + } + + // Create toolbar container at bottom + if (lo == 'bottom') + t._addToolbars(tb, o); + + if (sl == 'bottom') + t._addStatusBar(tb, o); + + return ic; + }, + + _rowLayout : function(s, tb, o) { + var t = this, ed = t.editor, dc, da, cf = ed.controlManager, n, ic, to, a; + + dc = s.theme_advanced_containers_default_class || ''; + da = s.theme_advanced_containers_default_align || 'center'; + + each(explode(s.theme_advanced_containers || ''), function(c, i) { + var v = s['theme_advanced_container_' + c] || ''; + + switch (v.toLowerCase()) { + case 'mceeditor': + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + break; + + case 'mceelementpath': + t._addStatusBar(tb, o); + break; + + default: + a = (s['theme_advanced_container_' + c + '_align'] || da).toLowerCase(); + a = 'mce' + t._ufirst(a); + + n = DOM.add(DOM.add(tb, 'tr'), 'td', { + 'class' : 'mceToolbar ' + (s['theme_advanced_container_' + c + '_class'] || dc) + ' ' + a || da + }); + + to = cf.createToolbar("toolbar" + i); + t._addControls(v, to); + DOM.setHTML(n, to.renderHTML()); + o.deltaHeight -= s.theme_advanced_row_height; + } + }); + + return ic; + }, + + _addControls : function(v, tb) { + var t = this, s = t.settings, di, cf = t.editor.controlManager; + + if (s.theme_advanced_disable && !t._disabled) { + di = {}; + + each(explode(s.theme_advanced_disable), function(v) { + di[v] = 1; + }); + + t._disabled = di; + } else + di = t._disabled; + + each(explode(v), function(n) { + var c; + + if (di && di[n]) + return; + + // Compatiblity with 2.x + if (n == 'tablecontrols') { + each(["table","|","row_props","cell_props","|","row_before","row_after","delete_row","|","col_before","col_after","delete_col","|","split_cells","merge_cells"], function(n) { + n = t.createControl(n, cf); + + if (n) + tb.add(n); + }); + + return; + } + + c = t.createControl(n, cf); + + if (c) + tb.add(c); + }); + }, + + _addToolbars : function(c, o) { + var t = this, i, tb, ed = t.editor, s = t.settings, v, cf = ed.controlManager, di, n, h = [], a; + + a = s.theme_advanced_toolbar_align.toLowerCase(); + a = 'mce' + t._ufirst(a); + + n = DOM.add(DOM.add(c, 'tr'), 'td', {'class' : 'mceToolbar ' + a}); + + if (!ed.getParam('accessibility_focus')) + h.push(DOM.createHTML('a', {href : '#', onfocus : 'tinyMCE.get(\'' + ed.id + '\').focus();'}, '')); + + h.push(DOM.createHTML('a', {href : '#', accesskey : 'q', title : ed.getLang("advanced.toolbar_focus")}, '')); + + // Create toolbar and add the controls + for (i=1; (v = s['theme_advanced_buttons' + i]); i++) { + tb = cf.createToolbar("toolbar" + i, {'class' : 'mceToolbarRow' + i}); + + if (s['theme_advanced_buttons' + i + '_add']) + v += ',' + s['theme_advanced_buttons' + i + '_add']; + + if (s['theme_advanced_buttons' + i + '_add_before']) + v = s['theme_advanced_buttons' + i + '_add_before'] + ',' + v; + + t._addControls(v, tb); + + //n.appendChild(n = tb.render()); + h.push(tb.renderHTML()); + + o.deltaHeight -= s.theme_advanced_row_height; + } + + h.push(DOM.createHTML('a', {href : '#', accesskey : 'z', title : ed.getLang("advanced.toolbar_focus"), onfocus : 'tinyMCE.getInstanceById(\'' + ed.id + '\').focus();'}, '')); + DOM.setHTML(n, h.join('')); + }, + + _addStatusBar : function(tb, o) { + var n, t = this, ed = t.editor, s = t.settings, r, mf, me, td; + + n = DOM.add(tb, 'tr'); + n = td = DOM.add(n, 'td', {'class' : 'mceStatusbar'}); + n = DOM.add(n, 'div', {id : ed.id + '_path_row'}, s.theme_advanced_path ? ed.translate('advanced.path') + ': ' : ' '); + DOM.add(n, 'a', {href : '#', accesskey : 'x'}); + + if (s.theme_advanced_resizing) { + DOM.add(td, 'a', {id : ed.id + '_resize', href : 'javascript:;', onclick : "return false;", 'class' : 'mceResize'}); + + if (s.theme_advanced_resizing_use_cookie) { + ed.onPostRender.add(function() { + var o = Cookie.getHash("TinyMCE_" + ed.id + "_size"), c = DOM.get(ed.id + '_tbl'); + + if (!o) + return; + + t.resizeTo(o.cw, o.ch); + }); + } + + ed.onPostRender.add(function() { + Event.add(ed.id + '_resize', 'click', function(e) { + e.preventDefault(); + }); + + Event.add(ed.id + '_resize', 'mousedown', function(e) { + var mouseMoveHandler1, mouseMoveHandler2, + mouseUpHandler1, mouseUpHandler2, + startX, startY, startWidth, startHeight, width, height, ifrElm; + + function resizeOnMove(e) { + e.preventDefault(); + + width = startWidth + (e.screenX - startX); + height = startHeight + (e.screenY - startY); + + t.resizeTo(width, height); + }; + + function endResize(e) { + // Stop listening + Event.remove(DOM.doc, 'mousemove', mouseMoveHandler1); + Event.remove(ed.getDoc(), 'mousemove', mouseMoveHandler2); + Event.remove(DOM.doc, 'mouseup', mouseUpHandler1); + Event.remove(ed.getDoc(), 'mouseup', mouseUpHandler2); + + width = startWidth + (e.screenX - startX); + height = startHeight + (e.screenY - startY); + t.resizeTo(width, height, true); + }; + + e.preventDefault(); + + // Get the current rect size + startX = e.screenX; + startY = e.screenY; + ifrElm = DOM.get(t.editor.id + '_ifr'); + startWidth = width = ifrElm.clientWidth; + startHeight = height = ifrElm.clientHeight; + + // Register envent handlers + mouseMoveHandler1 = Event.add(DOM.doc, 'mousemove', resizeOnMove); + mouseMoveHandler2 = Event.add(ed.getDoc(), 'mousemove', resizeOnMove); + mouseUpHandler1 = Event.add(DOM.doc, 'mouseup', endResize); + mouseUpHandler2 = Event.add(ed.getDoc(), 'mouseup', endResize); + }); + }); + } + + o.deltaHeight -= 21; + n = tb = null; + }, + + _nodeChanged : function(ed, cm, n, co, ob) { + var t = this, p, de = 0, v, c, s = t.settings, cl, fz, fn, formatNames, matches; + + tinymce.each(t.stateControls, function(c) { + cm.setActive(c, ed.queryCommandState(t.controls[c][1])); + }); + + function getParent(name) { + var i, parents = ob.parents, func = name; + + if (typeof(name) == 'string') { + func = function(node) { + return node.nodeName == name; + }; + } + + for (i = 0; i < parents.length; i++) { + if (func(parents[i])) + return parents[i]; + } + }; + + cm.setActive('visualaid', ed.hasVisual); + cm.setDisabled('undo', !ed.undoManager.hasUndo() && !ed.typing); + cm.setDisabled('redo', !ed.undoManager.hasRedo()); + cm.setDisabled('outdent', !ed.queryCommandState('Outdent')); + + p = getParent('A'); + if (c = cm.get('link')) { + if (!p || !p.name) { + c.setDisabled(!p && co); + c.setActive(!!p); + } + } + + if (c = cm.get('unlink')) { + c.setDisabled(!p && co); + c.setActive(!!p && !p.name); + } + + if (c = cm.get('anchor')) { + c.setActive(!!p && p.name); + } + + p = getParent('IMG'); + if (c = cm.get('image')) + c.setActive(!!p && n.className.indexOf('mceItem') == -1); + + if (c = cm.get('styleselect')) { + t._importClasses(); + + formatNames = []; + each(c.items, function(item) { + formatNames.push(item.value); + }); + + matches = ed.formatter.matchAll(formatNames); + c.select(matches[0]); + } + + if (c = cm.get('formatselect')) { + p = getParent(DOM.isBlock); + + if (p) + c.select(p.nodeName.toLowerCase()); + } + + // Find out current fontSize, fontFamily and fontClass + getParent(function(n) { + if (n.nodeName === 'SPAN') { + if (!cl && n.className) + cl = n.className; + + if (!fz && n.style.fontSize) + fz = n.style.fontSize; + + if (!fn && n.style.fontFamily) + fn = n.style.fontFamily.replace(/[\"\']+/g, '').replace(/^([^,]+).*/, '$1').toLowerCase(); + } + + return false; + }); + + if (c = cm.get('fontselect')) { + c.select(function(v) { + return v.replace(/^([^,]+).*/, '$1').toLowerCase() == fn; + }); + } + + // Select font size + if (c = cm.get('fontsizeselect')) { + // Use computed style + if (s.theme_advanced_runtime_fontsize && !fz && !cl) + fz = ed.dom.getStyle(n, 'fontSize', true); + + c.select(function(v) { + if (v.fontSize && v.fontSize === fz) + return true; + + if (v['class'] && v['class'] === cl) + return true; + }); + } + + if (s.theme_advanced_path && s.theme_advanced_statusbar_location) { + p = DOM.get(ed.id + '_path') || DOM.add(ed.id + '_path_row', 'span', {id : ed.id + '_path'}); + DOM.setHTML(p, ''); + + getParent(function(n) { + var na = n.nodeName.toLowerCase(), u, pi, ti = ''; + + /*if (n.getAttribute('_mce_bogus')) + return; +*/ + // Ignore non element and hidden elements + if (n.nodeType != 1 || n.nodeName === 'BR' || (DOM.hasClass(n, 'mceItemHidden') || DOM.hasClass(n, 'mceItemRemoved'))) + return; + + // Fake name + if (v = DOM.getAttrib(n, 'mce_name')) + na = v; + + // Handle prefix + if (tinymce.isIE && n.scopeName !== 'HTML') + na = n.scopeName + ':' + na; + + // Remove internal prefix + na = na.replace(/mce\:/g, ''); + + // Handle node name + switch (na) { + case 'b': + na = 'strong'; + break; + + case 'i': + na = 'em'; + break; + + case 'img': + if (v = DOM.getAttrib(n, 'src')) + ti += 'src: ' + v + ' '; + + break; + + case 'a': + if (v = DOM.getAttrib(n, 'name')) { + ti += 'name: ' + v + ' '; + na += '#' + v; + } + + if (v = DOM.getAttrib(n, 'href')) + ti += 'href: ' + v + ' '; + + break; + + case 'font': + if (v = DOM.getAttrib(n, 'face')) + ti += 'font: ' + v + ' '; + + if (v = DOM.getAttrib(n, 'size')) + ti += 'size: ' + v + ' '; + + if (v = DOM.getAttrib(n, 'color')) + ti += 'color: ' + v + ' '; + + break; + + case 'span': + if (v = DOM.getAttrib(n, 'style')) + ti += 'style: ' + v + ' '; + + break; + } + + if (v = DOM.getAttrib(n, 'id')) + ti += 'id: ' + v + ' '; + + if (v = n.className) { + v = v.replace(/\b\s*(webkit|mce|Apple-)\w+\s*\b/g, '') + + if (v) { + ti += 'class: ' + v + ' '; + + if (DOM.isBlock(n) || na == 'img' || na == 'span') + na += '.' + v; + } + } + + na = na.replace(/(html:)/g, ''); + na = {name : na, node : n, title : ti}; + t.onResolveName.dispatch(t, na); + ti = na.title; + na = na.name; + + //u = "javascript:tinymce.EditorManager.get('" + ed.id + "').theme._sel('" + (de++) + "');"; + pi = DOM.create('a', {'href' : "javascript:;", onmousedown : "return false;", title : ti, 'class' : 'mcePath_' + (de++)}, na); + + if (p.hasChildNodes()) { + p.insertBefore(DOM.doc.createTextNode(' \u00bb '), p.firstChild); + p.insertBefore(pi, p.firstChild); + } else + p.appendChild(pi); + }, ed.getBody()); + } + }, + + // Commands gets called by execCommand + + _sel : function(v) { + this.editor.execCommand('mceSelectNodeDepth', false, v); + }, + + _mceInsertAnchor : function(ui, v) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/anchor.htm', + width : 320 + parseInt(ed.getLang('advanced.anchor_delta_width', 0)), + height : 90 + parseInt(ed.getLang('advanced.anchor_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceCharMap : function() { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/charmap.htm', + width : 550 + parseInt(ed.getLang('advanced.charmap_delta_width', 0)), + height : 250 + parseInt(ed.getLang('advanced.charmap_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceHelp : function() { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/about.htm', + width : 480, + height : 380, + inline : true + }, { + theme_url : this.url + }); + }, + + _mceColorPicker : function(u, v) { + var ed = this.editor; + + v = v || {}; + + ed.windowManager.open({ + url : this.url + '/color_picker.htm', + width : 375 + parseInt(ed.getLang('advanced.colorpicker_delta_width', 0)), + height : 250 + parseInt(ed.getLang('advanced.colorpicker_delta_height', 0)), + close_previous : false, + inline : true + }, { + input_color : v.color, + func : v.func, + theme_url : this.url + }); + }, + + _mceCodeEditor : function(ui, val) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/source_editor.htm', + width : parseInt(ed.getParam("theme_advanced_source_editor_width", 720)), + height : parseInt(ed.getParam("theme_advanced_source_editor_height", 580)), + inline : true, + resizable : true, + maximizable : true + }, { + theme_url : this.url + }); + }, + + _mceImage : function(ui, val) { + var ed = this.editor; + + // Internal image object like a flash placeholder + if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1) + return; + + ed.windowManager.open({ + url : this.url + '/image.htm', + width : 355 + parseInt(ed.getLang('advanced.image_delta_width', 0)), + height : 275 + parseInt(ed.getLang('advanced.image_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceLink : function(ui, val) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/link.htm', + width : 310 + parseInt(ed.getLang('advanced.link_delta_width', 0)), + height : 200 + parseInt(ed.getLang('advanced.link_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceNewDocument : function() { + var ed = this.editor; + + ed.windowManager.confirm('advanced.newdocument', function(s) { + if (s) + ed.execCommand('mceSetContent', false, ''); + }); + }, + + _mceForeColor : function() { + var t = this; + + this._mceColorPicker(0, { + color: t.fgColor, + func : function(co) { + t.fgColor = co; + t.editor.execCommand('ForeColor', false, co); + } + }); + }, + + _mceBackColor : function() { + var t = this; + + this._mceColorPicker(0, { + color: t.bgColor, + func : function(co) { + t.bgColor = co; + t.editor.execCommand('HiliteColor', false, co); + } + }); + }, + + _ufirst : function(s) { + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + }); + + tinymce.ThemeManager.add('advanced', tinymce.themes.AdvancedTheme); +}(tinymce)); \ No newline at end of file diff --git a/web/js/globals/tinymce/themes/advanced/image.htm b/web/js/globals/tinymce/themes/advanced/image.htm new file mode 100644 index 0000000..f30d670 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/image.htm @@ -0,0 +1,80 @@ + + + + {#advanced_dlg.image_title} + + + + + + +
      + + +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
       
      + x +
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/themes/advanced/img/colorpicker.jpg b/web/js/globals/tinymce/themes/advanced/img/colorpicker.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b4c542d107b25f68a9d4f9d7a109d0565d1f1437 GIT binary patch literal 3189 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 literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/themes/advanced/img/icons.gif b/web/js/globals/tinymce/themes/advanced/img/icons.gif new file mode 100644 index 0000000000000000000000000000000000000000..e46de5333082869b9bdab2576a554a2f9d01a966 GIT binary patch literal 11794 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 F{{gR&nqvR} literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/themes/advanced/js/about.js b/web/js/globals/tinymce/themes/advanced/js/about.js new file mode 100644 index 0000000..5cee9ed --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/js/about.js @@ -0,0 +1,72 @@ +tinyMCEPopup.requireLangPack(); + +function init() { + var ed, tcont; + + tinyMCEPopup.resizeToInnerSize(); + ed = tinyMCEPopup.editor; + + // Give FF some time + window.setTimeout(insertHelpIFrame, 10); + + tcont = document.getElementById('plugintablecontainer'); + document.getElementById('plugins_tab').style.display = 'none'; + + var html = ""; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + tinymce.each(ed.plugins, function(p, n) { + var info; + + if (!p.getInfo) + return; + + html += ''; + + info = p.getInfo(); + + if (info.infourl != null && info.infourl != '') + html += ''; + else + html += ''; + + if (info.authorurl != null && info.authorurl != '') + html += ''; + else + html += ''; + + html += ''; + html += ''; + + document.getElementById('plugins_tab').style.display = ''; + + }); + + html += ''; + html += '
      ' + ed.getLang('advanced_dlg.about_plugin') + '' + ed.getLang('advanced_dlg.about_author') + '' + ed.getLang('advanced_dlg.about_version') + '
      ' + info.longname + '' + info.longname + '' + info.author + '' + info.author + '' + info.version + '
      '; + + tcont.innerHTML = html; + + tinyMCEPopup.dom.get('version').innerHTML = tinymce.majorVersion + "." + tinymce.minorVersion; + tinyMCEPopup.dom.get('date').innerHTML = tinymce.releaseDate; +} + +function insertHelpIFrame() { + var html; + + if (tinyMCEPopup.getParam('docs_url')) { + html = ''; + document.getElementById('iframecontainer').innerHTML = html; + document.getElementById('help_tab').style.display = 'block'; + } +} + +tinyMCEPopup.onInit.add(init); diff --git a/web/js/globals/tinymce/themes/advanced/js/anchor.js b/web/js/globals/tinymce/themes/advanced/js/anchor.js new file mode 100644 index 0000000..7fe7810 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/js/anchor.js @@ -0,0 +1,37 @@ +tinyMCEPopup.requireLangPack(); + +var AnchorDialog = { + init : function(ed) { + var action, elm, f = document.forms[0]; + + this.editor = ed; + elm = ed.dom.getParent(ed.selection.getNode(), 'A'); + v = ed.dom.getAttrib(elm, 'name'); + + if (v) { + this.action = 'update'; + f.anchorName.value = v; + } + + f.insert.value = ed.getLang(elm ? 'update' : 'insert'); + }, + + update : function() { + var ed = this.editor, elm, name = document.forms[0].anchorName.value; + + tinyMCEPopup.restoreSelection(); + + if (this.action != 'update') + ed.selection.collapse(1); + + elm = ed.dom.getParent(ed.selection.getNode(), 'A'); + if (elm) + elm.name = name; + else + ed.execCommand('mceInsertContent', 0, ed.dom.createHTML('a', {name : name, 'class' : 'mceItemAnchor'}, '')); + + tinyMCEPopup.close(); + } +}; + +tinyMCEPopup.onInit.add(AnchorDialog.init, AnchorDialog); diff --git a/web/js/globals/tinymce/themes/advanced/js/charmap.js b/web/js/globals/tinymce/themes/advanced/js/charmap.js new file mode 100644 index 0000000..8c5aea1 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/js/charmap.js @@ -0,0 +1,335 @@ +/** + * charmap.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinyMCEPopup.requireLangPack(); + +var charmap = [ + [' ', ' ', true, 'no-break space'], + ['&', '&', true, 'ampersand'], + ['"', '"', true, 'quotation mark'], +// finance + ['¢', '¢', true, 'cent sign'], + ['€', '€', true, 'euro sign'], + ['£', '£', true, 'pound sign'], + ['¥', '¥', true, 'yen sign'], +// signs + ['©', '©', true, 'copyright sign'], + ['®', '®', true, 'registered sign'], + ['™', '™', true, 'trade mark sign'], + ['‰', '‰', true, 'per mille sign'], + ['µ', 'µ', true, 'micro sign'], + ['·', '·', true, 'middle dot'], + ['•', '•', true, 'bullet'], + ['…', '…', true, 'three dot leader'], + ['′', '′', true, 'minutes / feet'], + ['″', '″', true, 'seconds / inches'], + ['§', '§', true, 'section sign'], + ['¶', '¶', true, 'paragraph sign'], + ['ß', 'ß', true, 'sharp s / ess-zed'], +// quotations + ['‹', '‹', true, 'single left-pointing angle quotation mark'], + ['›', '›', true, 'single right-pointing angle quotation mark'], + ['«', '«', true, 'left pointing guillemet'], + ['»', '»', true, 'right pointing guillemet'], + ['‘', '‘', true, 'left single quotation mark'], + ['’', '’', true, 'right single quotation mark'], + ['“', '“', true, 'left double quotation mark'], + ['”', '”', true, 'right double quotation mark'], + ['‚', '‚', true, 'single low-9 quotation mark'], + ['„', '„', true, 'double low-9 quotation mark'], + ['<', '<', true, 'less-than sign'], + ['>', '>', true, 'greater-than sign'], + ['≤', '≤', true, 'less-than or equal to'], + ['≥', '≥', true, 'greater-than or equal to'], + ['–', '–', true, 'en dash'], + ['—', '—', true, 'em dash'], + ['¯', '¯', true, 'macron'], + ['‾', '‾', true, 'overline'], + ['¤', '¤', true, 'currency sign'], + ['¦', '¦', true, 'broken bar'], + ['¨', '¨', true, 'diaeresis'], + ['¡', '¡', true, 'inverted exclamation mark'], + ['¿', '¿', true, 'turned question mark'], + ['ˆ', 'ˆ', true, 'circumflex accent'], + ['˜', '˜', true, 'small tilde'], + ['°', '°', true, 'degree sign'], + ['−', '−', true, 'minus sign'], + ['±', '±', true, 'plus-minus sign'], + ['÷', '÷', true, 'division sign'], + ['⁄', '⁄', true, 'fraction slash'], + ['×', '×', true, 'multiplication sign'], + ['¹', '¹', true, 'superscript one'], + ['²', '²', true, 'superscript two'], + ['³', '³', true, 'superscript three'], + ['¼', '¼', true, 'fraction one quarter'], + ['½', '½', true, 'fraction one half'], + ['¾', '¾', true, 'fraction three quarters'], +// math / logical + ['ƒ', 'ƒ', true, 'function / florin'], + ['∫', '∫', true, 'integral'], + ['∑', '∑', true, 'n-ary sumation'], + ['∞', '∞', true, 'infinity'], + ['√', '√', true, 'square root'], + ['∼', '∼', false,'similar to'], + ['≅', '≅', false,'approximately equal to'], + ['≈', '≈', true, 'almost equal to'], + ['≠', '≠', true, 'not equal to'], + ['≡', '≡', true, 'identical to'], + ['∈', '∈', false,'element of'], + ['∉', '∉', false,'not an element of'], + ['∋', '∋', false,'contains as member'], + ['∏', '∏', true, 'n-ary product'], + ['∧', '∧', false,'logical and'], + ['∨', '∨', false,'logical or'], + ['¬', '¬', true, 'not sign'], + ['∩', '∩', true, 'intersection'], + ['∪', '∪', false,'union'], + ['∂', '∂', true, 'partial differential'], + ['∀', '∀', false,'for all'], + ['∃', '∃', false,'there exists'], + ['∅', '∅', false,'diameter'], + ['∇', '∇', false,'backward difference'], + ['∗', '∗', false,'asterisk operator'], + ['∝', '∝', false,'proportional to'], + ['∠', '∠', false,'angle'], +// undefined + ['´', '´', true, 'acute accent'], + ['¸', '¸', true, 'cedilla'], + ['ª', 'ª', true, 'feminine ordinal indicator'], + ['º', 'º', true, 'masculine ordinal indicator'], + ['†', '†', true, 'dagger'], + ['‡', '‡', true, 'double dagger'], +// alphabetical special chars + ['À', 'À', true, 'A - grave'], + ['Á', 'Á', true, 'A - acute'], + ['Â', 'Â', true, 'A - circumflex'], + ['Ã', 'Ã', true, 'A - tilde'], + ['Ä', 'Ä', true, 'A - diaeresis'], + ['Å', 'Å', true, 'A - ring above'], + ['Æ', 'Æ', true, 'ligature AE'], + ['Ç', 'Ç', true, 'C - cedilla'], + ['È', 'È', true, 'E - grave'], + ['É', 'É', true, 'E - acute'], + ['Ê', 'Ê', true, 'E - circumflex'], + ['Ë', 'Ë', true, 'E - diaeresis'], + ['Ì', 'Ì', true, 'I - grave'], + ['Í', 'Í', true, 'I - acute'], + ['Î', 'Î', true, 'I - circumflex'], + ['Ï', 'Ï', true, 'I - diaeresis'], + ['Ð', 'Ð', true, 'ETH'], + ['Ñ', 'Ñ', true, 'N - tilde'], + ['Ò', 'Ò', true, 'O - grave'], + ['Ó', 'Ó', true, 'O - acute'], + ['Ô', 'Ô', true, 'O - circumflex'], + ['Õ', 'Õ', true, 'O - tilde'], + ['Ö', 'Ö', true, 'O - diaeresis'], + ['Ø', 'Ø', true, 'O - slash'], + ['Œ', 'Œ', true, 'ligature OE'], + ['Š', 'Š', true, 'S - caron'], + ['Ù', 'Ù', true, 'U - grave'], + ['Ú', 'Ú', true, 'U - acute'], + ['Û', 'Û', true, 'U - circumflex'], + ['Ü', 'Ü', true, 'U - diaeresis'], + ['Ý', 'Ý', true, 'Y - acute'], + ['Ÿ', 'Ÿ', true, 'Y - diaeresis'], + ['Þ', 'Þ', true, 'THORN'], + ['à', 'à', true, 'a - grave'], + ['á', 'á', true, 'a - acute'], + ['â', 'â', true, 'a - circumflex'], + ['ã', 'ã', true, 'a - tilde'], + ['ä', 'ä', true, 'a - diaeresis'], + ['å', 'å', true, 'a - ring above'], + ['æ', 'æ', true, 'ligature ae'], + ['ç', 'ç', true, 'c - cedilla'], + ['è', 'è', true, 'e - grave'], + ['é', 'é', true, 'e - acute'], + ['ê', 'ê', true, 'e - circumflex'], + ['ë', 'ë', true, 'e - diaeresis'], + ['ì', 'ì', true, 'i - grave'], + ['í', 'í', true, 'i - acute'], + ['î', 'î', true, 'i - circumflex'], + ['ï', 'ï', true, 'i - diaeresis'], + ['ð', 'ð', true, 'eth'], + ['ñ', 'ñ', true, 'n - tilde'], + ['ò', 'ò', true, 'o - grave'], + ['ó', 'ó', true, 'o - acute'], + ['ô', 'ô', true, 'o - circumflex'], + ['õ', 'õ', true, 'o - tilde'], + ['ö', 'ö', true, 'o - diaeresis'], + ['ø', 'ø', true, 'o slash'], + ['œ', 'œ', true, 'ligature oe'], + ['š', 'š', true, 's - caron'], + ['ù', 'ù', true, 'u - grave'], + ['ú', 'ú', true, 'u - acute'], + ['û', 'û', true, 'u - circumflex'], + ['ü', 'ü', true, 'u - diaeresis'], + ['ý', 'ý', true, 'y - acute'], + ['þ', 'þ', true, 'thorn'], + ['ÿ', 'ÿ', true, 'y - diaeresis'], + ['Α', 'Α', true, 'Alpha'], + ['Β', 'Β', true, 'Beta'], + ['Γ', 'Γ', true, 'Gamma'], + ['Δ', 'Δ', true, 'Delta'], + ['Ε', 'Ε', true, 'Epsilon'], + ['Ζ', 'Ζ', true, 'Zeta'], + ['Η', 'Η', true, 'Eta'], + ['Θ', 'Θ', true, 'Theta'], + ['Ι', 'Ι', true, 'Iota'], + ['Κ', 'Κ', true, 'Kappa'], + ['Λ', 'Λ', true, 'Lambda'], + ['Μ', 'Μ', true, 'Mu'], + ['Ν', 'Ν', true, 'Nu'], + ['Ξ', 'Ξ', true, 'Xi'], + ['Ο', 'Ο', true, 'Omicron'], + ['Π', 'Π', true, 'Pi'], + ['Ρ', 'Ρ', true, 'Rho'], + ['Σ', 'Σ', true, 'Sigma'], + ['Τ', 'Τ', true, 'Tau'], + ['Υ', 'Υ', true, 'Upsilon'], + ['Φ', 'Φ', true, 'Phi'], + ['Χ', 'Χ', true, 'Chi'], + ['Ψ', 'Ψ', true, 'Psi'], + ['Ω', 'Ω', true, 'Omega'], + ['α', 'α', true, 'alpha'], + ['β', 'β', true, 'beta'], + ['γ', 'γ', true, 'gamma'], + ['δ', 'δ', true, 'delta'], + ['ε', 'ε', true, 'epsilon'], + ['ζ', 'ζ', true, 'zeta'], + ['η', 'η', true, 'eta'], + ['θ', 'θ', true, 'theta'], + ['ι', 'ι', true, 'iota'], + ['κ', 'κ', true, 'kappa'], + ['λ', 'λ', true, 'lambda'], + ['μ', 'μ', true, 'mu'], + ['ν', 'ν', true, 'nu'], + ['ξ', 'ξ', true, 'xi'], + ['ο', 'ο', true, 'omicron'], + ['π', 'π', true, 'pi'], + ['ρ', 'ρ', true, 'rho'], + ['ς', 'ς', true, 'final sigma'], + ['σ', 'σ', true, 'sigma'], + ['τ', 'τ', true, 'tau'], + ['υ', 'υ', true, 'upsilon'], + ['φ', 'φ', true, 'phi'], + ['χ', 'χ', true, 'chi'], + ['ψ', 'ψ', true, 'psi'], + ['ω', 'ω', true, 'omega'], +// symbols + ['ℵ', 'ℵ', false,'alef symbol'], + ['ϖ', 'ϖ', false,'pi symbol'], + ['ℜ', 'ℜ', false,'real part symbol'], + ['ϑ','ϑ', false,'theta symbol'], + ['ϒ', 'ϒ', false,'upsilon - hook symbol'], + ['℘', '℘', false,'Weierstrass p'], + ['ℑ', 'ℑ', false,'imaginary part'], +// arrows + ['←', '←', true, 'leftwards arrow'], + ['↑', '↑', true, 'upwards arrow'], + ['→', '→', true, 'rightwards arrow'], + ['↓', '↓', true, 'downwards arrow'], + ['↔', '↔', true, 'left right arrow'], + ['↵', '↵', false,'carriage return'], + ['⇐', '⇐', false,'leftwards double arrow'], + ['⇑', '⇑', false,'upwards double arrow'], + ['⇒', '⇒', false,'rightwards double arrow'], + ['⇓', '⇓', false,'downwards double arrow'], + ['⇔', '⇔', false,'left right double arrow'], + ['∴', '∴', false,'therefore'], + ['⊂', '⊂', false,'subset of'], + ['⊃', '⊃', false,'superset of'], + ['⊄', '⊄', false,'not a subset of'], + ['⊆', '⊆', false,'subset of or equal to'], + ['⊇', '⊇', false,'superset of or equal to'], + ['⊕', '⊕', false,'circled plus'], + ['⊗', '⊗', false,'circled times'], + ['⊥', '⊥', false,'perpendicular'], + ['⋅', '⋅', false,'dot operator'], + ['⌈', '⌈', false,'left ceiling'], + ['⌉', '⌉', false,'right ceiling'], + ['⌊', '⌊', false,'left floor'], + ['⌋', '⌋', false,'right floor'], + ['⟨', '〈', false,'left-pointing angle bracket'], + ['⟩', '〉', false,'right-pointing angle bracket'], + ['◊', '◊', true,'lozenge'], + ['♠', '♠', false,'black spade suit'], + ['♣', '♣', true, 'black club suit'], + ['♥', '♥', true, 'black heart suit'], + ['♦', '♦', true, 'black diamond suit'], + [' ', ' ', false,'en space'], + [' ', ' ', false,'em space'], + [' ', ' ', false,'thin space'], + ['‌', '‌', false,'zero width non-joiner'], + ['‍', '‍', false,'zero width joiner'], + ['‎', '‎', false,'left-to-right mark'], + ['‏', '‏', false,'right-to-left mark'], + ['­', '­', false,'soft hyphen'] +]; + +tinyMCEPopup.onInit.add(function() { + tinyMCEPopup.dom.setHTML('charmapView', renderCharMapHTML()); +}); + +function renderCharMapHTML() { + var charsPerRow = 20, tdWidth=20, tdHeight=20, i; + var html = ''; + var cols=-1; + + for (i=0; i' + + '' + + charmap[i][1] + + ''; + if ((cols+1) % charsPerRow == 0) + html += ''; + } + } + + if (cols % charsPerRow > 0) { + var padd = charsPerRow - (cols % charsPerRow); + for (var i=0; i '; + } + + html += '
      '; + + return html; +} + +function insertChar(chr) { + tinyMCEPopup.execCommand('mceInsertContent', false, '&#' + chr + ';'); + + // Refocus in window + if (tinyMCEPopup.isWindow) + window.focus(); + + tinyMCEPopup.editor.focus(); + tinyMCEPopup.close(); +} + +function previewChar(codeA, codeB, codeN) { + var elmA = document.getElementById('codeA'); + var elmB = document.getElementById('codeB'); + var elmV = document.getElementById('codeV'); + var elmN = document.getElementById('codeN'); + + if (codeA=='#160;') { + elmV.innerHTML = '__'; + } else { + elmV.innerHTML = '&' + codeA; + } + + elmB.innerHTML = '&' + codeA; + elmA.innerHTML = '&' + codeB; + elmN.innerHTML = codeN; +} diff --git a/web/js/globals/tinymce/themes/advanced/js/color_picker.js b/web/js/globals/tinymce/themes/advanced/js/color_picker.js new file mode 100644 index 0000000..fd9700f --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/js/color_picker.js @@ -0,0 +1,253 @@ +tinyMCEPopup.requireLangPack(); + +var detail = 50, strhex = "0123456789abcdef", i, isMouseDown = false, isMouseOver = false; + +var colors = [ + "#000000","#000033","#000066","#000099","#0000cc","#0000ff","#330000","#330033", + "#330066","#330099","#3300cc","#3300ff","#660000","#660033","#660066","#660099", + "#6600cc","#6600ff","#990000","#990033","#990066","#990099","#9900cc","#9900ff", + "#cc0000","#cc0033","#cc0066","#cc0099","#cc00cc","#cc00ff","#ff0000","#ff0033", + "#ff0066","#ff0099","#ff00cc","#ff00ff","#003300","#003333","#003366","#003399", + "#0033cc","#0033ff","#333300","#333333","#333366","#333399","#3333cc","#3333ff", + "#663300","#663333","#663366","#663399","#6633cc","#6633ff","#993300","#993333", + "#993366","#993399","#9933cc","#9933ff","#cc3300","#cc3333","#cc3366","#cc3399", + "#cc33cc","#cc33ff","#ff3300","#ff3333","#ff3366","#ff3399","#ff33cc","#ff33ff", + "#006600","#006633","#006666","#006699","#0066cc","#0066ff","#336600","#336633", + "#336666","#336699","#3366cc","#3366ff","#666600","#666633","#666666","#666699", + "#6666cc","#6666ff","#996600","#996633","#996666","#996699","#9966cc","#9966ff", + "#cc6600","#cc6633","#cc6666","#cc6699","#cc66cc","#cc66ff","#ff6600","#ff6633", + "#ff6666","#ff6699","#ff66cc","#ff66ff","#009900","#009933","#009966","#009999", + "#0099cc","#0099ff","#339900","#339933","#339966","#339999","#3399cc","#3399ff", + "#669900","#669933","#669966","#669999","#6699cc","#6699ff","#999900","#999933", + "#999966","#999999","#9999cc","#9999ff","#cc9900","#cc9933","#cc9966","#cc9999", + "#cc99cc","#cc99ff","#ff9900","#ff9933","#ff9966","#ff9999","#ff99cc","#ff99ff", + "#00cc00","#00cc33","#00cc66","#00cc99","#00cccc","#00ccff","#33cc00","#33cc33", + "#33cc66","#33cc99","#33cccc","#33ccff","#66cc00","#66cc33","#66cc66","#66cc99", + "#66cccc","#66ccff","#99cc00","#99cc33","#99cc66","#99cc99","#99cccc","#99ccff", + "#cccc00","#cccc33","#cccc66","#cccc99","#cccccc","#ccccff","#ffcc00","#ffcc33", + "#ffcc66","#ffcc99","#ffcccc","#ffccff","#00ff00","#00ff33","#00ff66","#00ff99", + "#00ffcc","#00ffff","#33ff00","#33ff33","#33ff66","#33ff99","#33ffcc","#33ffff", + "#66ff00","#66ff33","#66ff66","#66ff99","#66ffcc","#66ffff","#99ff00","#99ff33", + "#99ff66","#99ff99","#99ffcc","#99ffff","#ccff00","#ccff33","#ccff66","#ccff99", + "#ccffcc","#ccffff","#ffff00","#ffff33","#ffff66","#ffff99","#ffffcc","#ffffff" +]; + +var named = { + '#F0F8FF':'AliceBlue','#FAEBD7':'AntiqueWhite','#00FFFF':'Aqua','#7FFFD4':'Aquamarine','#F0FFFF':'Azure','#F5F5DC':'Beige', + '#FFE4C4':'Bisque','#000000':'Black','#FFEBCD':'BlanchedAlmond','#0000FF':'Blue','#8A2BE2':'BlueViolet','#A52A2A':'Brown', + '#DEB887':'BurlyWood','#5F9EA0':'CadetBlue','#7FFF00':'Chartreuse','#D2691E':'Chocolate','#FF7F50':'Coral','#6495ED':'CornflowerBlue', + '#FFF8DC':'Cornsilk','#DC143C':'Crimson','#00FFFF':'Cyan','#00008B':'DarkBlue','#008B8B':'DarkCyan','#B8860B':'DarkGoldenRod', + '#A9A9A9':'DarkGray','#A9A9A9':'DarkGrey','#006400':'DarkGreen','#BDB76B':'DarkKhaki','#8B008B':'DarkMagenta','#556B2F':'DarkOliveGreen', + '#FF8C00':'Darkorange','#9932CC':'DarkOrchid','#8B0000':'DarkRed','#E9967A':'DarkSalmon','#8FBC8F':'DarkSeaGreen','#483D8B':'DarkSlateBlue', + '#2F4F4F':'DarkSlateGray','#2F4F4F':'DarkSlateGrey','#00CED1':'DarkTurquoise','#9400D3':'DarkViolet','#FF1493':'DeepPink','#00BFFF':'DeepSkyBlue', + '#696969':'DimGray','#696969':'DimGrey','#1E90FF':'DodgerBlue','#B22222':'FireBrick','#FFFAF0':'FloralWhite','#228B22':'ForestGreen', + '#FF00FF':'Fuchsia','#DCDCDC':'Gainsboro','#F8F8FF':'GhostWhite','#FFD700':'Gold','#DAA520':'GoldenRod','#808080':'Gray','#808080':'Grey', + '#008000':'Green','#ADFF2F':'GreenYellow','#F0FFF0':'HoneyDew','#FF69B4':'HotPink','#CD5C5C':'IndianRed','#4B0082':'Indigo','#FFFFF0':'Ivory', + '#F0E68C':'Khaki','#E6E6FA':'Lavender','#FFF0F5':'LavenderBlush','#7CFC00':'LawnGreen','#FFFACD':'LemonChiffon','#ADD8E6':'LightBlue', + '#F08080':'LightCoral','#E0FFFF':'LightCyan','#FAFAD2':'LightGoldenRodYellow','#D3D3D3':'LightGray','#D3D3D3':'LightGrey','#90EE90':'LightGreen', + '#FFB6C1':'LightPink','#FFA07A':'LightSalmon','#20B2AA':'LightSeaGreen','#87CEFA':'LightSkyBlue','#778899':'LightSlateGray','#778899':'LightSlateGrey', + '#B0C4DE':'LightSteelBlue','#FFFFE0':'LightYellow','#00FF00':'Lime','#32CD32':'LimeGreen','#FAF0E6':'Linen','#FF00FF':'Magenta','#800000':'Maroon', + '#66CDAA':'MediumAquaMarine','#0000CD':'MediumBlue','#BA55D3':'MediumOrchid','#9370D8':'MediumPurple','#3CB371':'MediumSeaGreen','#7B68EE':'MediumSlateBlue', + '#00FA9A':'MediumSpringGreen','#48D1CC':'MediumTurquoise','#C71585':'MediumVioletRed','#191970':'MidnightBlue','#F5FFFA':'MintCream','#FFE4E1':'MistyRose','#FFE4B5':'Moccasin', + '#FFDEAD':'NavajoWhite','#000080':'Navy','#FDF5E6':'OldLace','#808000':'Olive','#6B8E23':'OliveDrab','#FFA500':'Orange','#FF4500':'OrangeRed','#DA70D6':'Orchid', + '#EEE8AA':'PaleGoldenRod','#98FB98':'PaleGreen','#AFEEEE':'PaleTurquoise','#D87093':'PaleVioletRed','#FFEFD5':'PapayaWhip','#FFDAB9':'PeachPuff', + '#CD853F':'Peru','#FFC0CB':'Pink','#DDA0DD':'Plum','#B0E0E6':'PowderBlue','#800080':'Purple','#FF0000':'Red','#BC8F8F':'RosyBrown','#4169E1':'RoyalBlue', + '#8B4513':'SaddleBrown','#FA8072':'Salmon','#F4A460':'SandyBrown','#2E8B57':'SeaGreen','#FFF5EE':'SeaShell','#A0522D':'Sienna','#C0C0C0':'Silver', + '#87CEEB':'SkyBlue','#6A5ACD':'SlateBlue','#708090':'SlateGray','#708090':'SlateGrey','#FFFAFA':'Snow','#00FF7F':'SpringGreen', + '#4682B4':'SteelBlue','#D2B48C':'Tan','#008080':'Teal','#D8BFD8':'Thistle','#FF6347':'Tomato','#40E0D0':'Turquoise','#EE82EE':'Violet', + '#F5DEB3':'Wheat','#FFFFFF':'White','#F5F5F5':'WhiteSmoke','#FFFF00':'Yellow','#9ACD32':'YellowGreen' +}; + +function init() { + var inputColor = convertRGBToHex(tinyMCEPopup.getWindowArg('input_color')); + + tinyMCEPopup.resizeToInnerSize(); + + generatePicker(); + + if (inputColor) { + changeFinalColor(inputColor); + + col = convertHexToRGB(inputColor); + + if (col) + updateLight(col.r, col.g, col.b); + } +} + +function insertAction() { + var color = document.getElementById("color").value, f = tinyMCEPopup.getWindowArg('func'); + + tinyMCEPopup.restoreSelection(); + + if (f) + f(color); + + tinyMCEPopup.close(); +} + +function showColor(color, name) { + if (name) + document.getElementById("colorname").innerHTML = name; + + document.getElementById("preview").style.backgroundColor = color; + document.getElementById("color").value = color.toLowerCase(); +} + +function convertRGBToHex(col) { + var re = new RegExp("rgb\\s*\\(\\s*([0-9]+).*,\\s*([0-9]+).*,\\s*([0-9]+).*\\)", "gi"); + + if (!col) + return col; + + var rgb = col.replace(re, "$1,$2,$3").split(','); + if (rgb.length == 3) { + r = parseInt(rgb[0]).toString(16); + g = parseInt(rgb[1]).toString(16); + b = parseInt(rgb[2]).toString(16); + + r = r.length == 1 ? '0' + r : r; + g = g.length == 1 ? '0' + g : g; + b = b.length == 1 ? '0' + b : b; + + return "#" + r + g + b; + } + + return col; +} + +function convertHexToRGB(col) { + if (col.indexOf('#') != -1) { + col = col.replace(new RegExp('[^0-9A-F]', 'gi'), ''); + + r = parseInt(col.substring(0, 2), 16); + g = parseInt(col.substring(2, 4), 16); + b = parseInt(col.substring(4, 6), 16); + + return {r : r, g : g, b : b}; + } + + return null; +} + +function generatePicker() { + var el = document.getElementById('light'), h = '', i; + + for (i = 0; i < detail; i++){ + h += '
      '; + } + + el.innerHTML = h; +} + +function generateWebColors() { + var el = document.getElementById('webcolors'), h = '', i; + + if (el.className == 'generated') + return; + + h += '' + + ''; + + for (i=0; i' + + '' + + ''; + if ((i+1) % 18 == 0) + h += ''; + } + + h += '
      '; + + el.innerHTML = h; + el.className = 'generated'; +} + +function generateNamedColors() { + var el = document.getElementById('namedcolors'), h = '', n, v, i = 0; + + if (el.className == 'generated') + return; + + for (n in named) { + v = named[n]; + h += '' + } + + el.innerHTML = h; + el.className = 'generated'; +} + +function dechex(n) { + return strhex.charAt(Math.floor(n / 16)) + strhex.charAt(n % 16); +} + +function computeColor(e) { + var x, y, partWidth, partDetail, imHeight, r, g, b, coef, i, finalCoef, finalR, finalG, finalB; + + x = e.offsetX ? e.offsetX : (e.target ? e.clientX - e.target.x : 0); + y = e.offsetY ? e.offsetY : (e.target ? e.clientY - e.target.y : 0); + + partWidth = document.getElementById('colors').width / 6; + partDetail = detail / 2; + imHeight = document.getElementById('colors').height; + + r = (x >= 0)*(x < partWidth)*255 + (x >= partWidth)*(x < 2*partWidth)*(2*255 - x * 255 / partWidth) + (x >= 4*partWidth)*(x < 5*partWidth)*(-4*255 + x * 255 / partWidth) + (x >= 5*partWidth)*(x < 6*partWidth)*255; + g = (x >= 0)*(x < partWidth)*(x * 255 / partWidth) + (x >= partWidth)*(x < 3*partWidth)*255 + (x >= 3*partWidth)*(x < 4*partWidth)*(4*255 - x * 255 / partWidth); + b = (x >= 2*partWidth)*(x < 3*partWidth)*(-2*255 + x * 255 / partWidth) + (x >= 3*partWidth)*(x < 5*partWidth)*255 + (x >= 5*partWidth)*(x < 6*partWidth)*(6*255 - x * 255 / partWidth); + + coef = (imHeight - y) / imHeight; + r = 128 + (r - 128) * coef; + g = 128 + (g - 128) * coef; + b = 128 + (b - 128) * coef; + + changeFinalColor('#' + dechex(r) + dechex(g) + dechex(b)); + updateLight(r, g, b); +} + +function updateLight(r, g, b) { + var i, partDetail = detail / 2, finalCoef, finalR, finalG, finalB, color; + + for (i=0; i=0) && (i'); + }, + + init : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor; + + // Setup browse button + document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); + if (isVisible('srcbrowser')) + document.getElementById('src').style.width = '180px'; + + e = ed.selection.getNode(); + + this.fillFileList('image_list', 'tinyMCEImageList'); + + if (e.nodeName == 'IMG') { + f.src.value = ed.dom.getAttrib(e, 'src'); + f.alt.value = ed.dom.getAttrib(e, 'alt'); + f.border.value = this.getAttrib(e, 'border'); + f.vspace.value = this.getAttrib(e, 'vspace'); + f.hspace.value = this.getAttrib(e, 'hspace'); + f.width.value = ed.dom.getAttrib(e, 'width'); + f.height.value = ed.dom.getAttrib(e, 'height'); + f.insert.value = ed.getLang('update'); + this.styleVal = ed.dom.getAttrib(e, 'style'); + selectByValue(f, 'image_list', f.src.value); + selectByValue(f, 'align', this.getAttrib(e, 'align')); + this.updateStyle(); + } + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = window[l]; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + update : function() { + var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, args = {}, el; + + tinyMCEPopup.restoreSelection(); + + if (f.src.value === '') { + if (ed.selection.getNode().nodeName == 'IMG') { + ed.dom.remove(ed.selection.getNode()); + ed.execCommand('mceRepaint'); + } + + tinyMCEPopup.close(); + return; + } + + if (!ed.settings.inline_styles) { + args = tinymce.extend(args, { + vspace : nl.vspace.value, + hspace : nl.hspace.value, + border : nl.border.value, + align : getSelectValue(f, 'align') + }); + } else + args.style = this.styleVal; + + tinymce.extend(args, { + src : f.src.value, + alt : f.alt.value, + width : f.width.value, + height : f.height.value + }); + + el = ed.selection.getNode(); + + if (el && el.nodeName == 'IMG') { + ed.dom.setAttribs(el, args); + } else { + ed.execCommand('mceInsertContent', false, '', {skip_undo : 1}); + ed.dom.setAttribs('__mce_tmp', args); + ed.dom.setAttrib('__mce_tmp', 'id', ''); + ed.undoManager.add(); + } + + tinyMCEPopup.close(); + }, + + updateStyle : function() { + var dom = tinyMCEPopup.dom, st, v, f = document.forms[0]; + + if (tinyMCEPopup.editor.settings.inline_styles) { + st = tinyMCEPopup.dom.parseStyle(this.styleVal); + + // Handle align + v = getSelectValue(f, 'align'); + if (v) { + if (v == 'left' || v == 'right') { + st['float'] = v; + delete st['vertical-align']; + } else { + st['vertical-align'] = v; + delete st['float']; + } + } else { + delete st['float']; + delete st['vertical-align']; + } + + // Handle border + v = f.border.value; + if (v || v == '0') { + if (v == '0') + st['border'] = '0'; + else + st['border'] = v + 'px solid black'; + } else + delete st['border']; + + // Handle hspace + v = f.hspace.value; + if (v) { + delete st['margin']; + st['margin-left'] = v + 'px'; + st['margin-right'] = v + 'px'; + } else { + delete st['margin-left']; + delete st['margin-right']; + } + + // Handle vspace + v = f.vspace.value; + if (v) { + delete st['margin']; + st['margin-top'] = v + 'px'; + st['margin-bottom'] = v + 'px'; + } else { + delete st['margin-top']; + delete st['margin-bottom']; + } + + // Merge + st = tinyMCEPopup.dom.parseStyle(dom.serializeStyle(st), 'img'); + this.styleVal = dom.serializeStyle(st, 'img'); + } + }, + + getAttrib : function(e, at) { + var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; + + if (ed.settings.inline_styles) { + switch (at) { + case 'align': + if (v = dom.getStyle(e, 'float')) + return v; + + if (v = dom.getStyle(e, 'vertical-align')) + return v; + + break; + + case 'hspace': + v = dom.getStyle(e, 'margin-left') + v2 = dom.getStyle(e, 'margin-right'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'vspace': + v = dom.getStyle(e, 'margin-top') + v2 = dom.getStyle(e, 'margin-bottom'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'border': + v = 0; + + tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { + sv = dom.getStyle(e, 'border-' + sv + '-width'); + + // False or not the same as prev + if (!sv || (sv != v && v !== 0)) { + v = 0; + return false; + } + + if (sv) + v = sv; + }); + + if (v) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + } + } + + if (v = dom.getAttrib(e, at)) + return v; + + return ''; + }, + + resetImageData : function() { + var f = document.forms[0]; + + f.width.value = f.height.value = ""; + }, + + updateImageData : function() { + var f = document.forms[0], t = ImageDialog; + + if (f.width.value == "") + f.width.value = t.preloadImg.width; + + if (f.height.value == "") + f.height.value = t.preloadImg.height; + }, + + getImageData : function() { + var f = document.forms[0]; + + this.preloadImg = new Image(); + this.preloadImg.onload = this.updateImageData; + this.preloadImg.onerror = this.resetImageData; + this.preloadImg.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(f.src.value); + } +}; + +ImageDialog.preInit(); +tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); diff --git a/web/js/globals/tinymce/themes/advanced/js/link.js b/web/js/globals/tinymce/themes/advanced/js/link.js new file mode 100644 index 0000000..f67a5bc --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/js/link.js @@ -0,0 +1,156 @@ +tinyMCEPopup.requireLangPack(); + +var LinkDialog = { + preInit : function() { + var url; + + if (url = tinyMCEPopup.getParam("external_link_list_url")) + document.write(''); + }, + + init : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor; + + // Setup browse button + document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser', 'href', 'file', 'theme_advanced_link'); + if (isVisible('hrefbrowser')) + document.getElementById('href').style.width = '180px'; + + this.fillClassList('class_list'); + this.fillFileList('link_list', 'tinyMCELinkList'); + this.fillTargetList('target_list'); + + if (e = ed.dom.getParent(ed.selection.getNode(), 'A')) { + f.href.value = ed.dom.getAttrib(e, 'href'); + f.linktitle.value = ed.dom.getAttrib(e, 'title'); + f.insert.value = ed.getLang('update'); + selectByValue(f, 'link_list', f.href.value); + selectByValue(f, 'target_list', ed.dom.getAttrib(e, 'target')); + selectByValue(f, 'class_list', ed.dom.getAttrib(e, 'class')); + } + }, + + update : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor, e, b; + + tinyMCEPopup.restoreSelection(); + e = ed.dom.getParent(ed.selection.getNode(), 'A'); + + // Remove element if there is no href + if (!f.href.value) { + if (e) { + tinyMCEPopup.execCommand("mceBeginUndoLevel"); + b = ed.selection.getBookmark(); + ed.dom.remove(e, 1); + ed.selection.moveToBookmark(b); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + return; + } + } + + tinyMCEPopup.execCommand("mceBeginUndoLevel"); + + // Create new anchor elements + if (e == null) { + ed.getDoc().execCommand("unlink", false, null); + tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1}); + + tinymce.each(ed.dom.select("a"), function(n) { + if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') { + e = n; + + ed.dom.setAttribs(e, { + href : f.href.value, + title : f.linktitle.value, + target : f.target_list ? getSelectValue(f, "target_list") : null, + 'class' : f.class_list ? getSelectValue(f, "class_list") : null + }); + } + }); + } else { + ed.dom.setAttribs(e, { + href : f.href.value, + title : f.linktitle.value, + target : f.target_list ? getSelectValue(f, "target_list") : null, + 'class' : f.class_list ? getSelectValue(f, "class_list") : null + }); + } + + // Don't move caret if selection was image + if (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') { + ed.focus(); + ed.selection.select(e); + ed.selection.collapse(0); + tinyMCEPopup.storeSelection(); + } + + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + }, + + checkPrefix : function(n) { + if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_email'))) + n.value = 'mailto:' + n.value; + + if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_external'))) + n.value = 'http://' + n.value; + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = window[l]; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillClassList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { + cl = []; + + tinymce.each(v.split(';'), function(v) { + var p = v.split('='); + + cl.push({'title' : p[0], 'class' : p[1]}); + }); + } else + cl = tinyMCEPopup.editor.dom.getClasses(); + + if (cl.length > 0) { + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + + tinymce.each(cl, function(o) { + lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillTargetList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v; + + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_same'), '_self'); + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_blank'), '_blank'); + + if (v = tinyMCEPopup.getParam('theme_advanced_link_targets')) { + tinymce.each(v.split(','), function(v) { + v = v.split('='); + lst.options[lst.options.length] = new Option(v[0], v[1]); + }); + } + } +}; + +LinkDialog.preInit(); +tinyMCEPopup.onInit.add(LinkDialog.init, LinkDialog); diff --git a/web/js/globals/tinymce/themes/advanced/js/source_editor.js b/web/js/globals/tinymce/themes/advanced/js/source_editor.js new file mode 100644 index 0000000..aca38bd --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/js/source_editor.js @@ -0,0 +1,56 @@ +tinyMCEPopup.requireLangPack(); +tinyMCEPopup.onInit.add(onLoadInit); + +function saveContent() { + tinyMCEPopup.editor.setContent(document.getElementById('htmlSource').value, {source_view : true}); + tinyMCEPopup.close(); +} + +function onLoadInit() { + tinyMCEPopup.resizeToInnerSize(); + + // Remove Gecko spellchecking + if (tinymce.isGecko) + document.body.spellcheck = tinyMCEPopup.editor.getParam("gecko_spellcheck"); + + document.getElementById('htmlSource').value = tinyMCEPopup.editor.getContent({source_view : true}); + + if (tinyMCEPopup.editor.getParam("theme_advanced_source_editor_wrap", true)) { + setWrap('soft'); + document.getElementById('wraped').checked = true; + } + + resizeInputs(); +} + +function setWrap(val) { + var v, n, s = document.getElementById('htmlSource'); + + s.wrap = val; + + if (!tinymce.isIE) { + v = s.value; + n = s.cloneNode(false); + n.setAttribute("wrap", val); + s.parentNode.replaceChild(n, s); + n.value = v; + } +} + +function toggleWordWrap(elm) { + if (elm.checked) + setWrap('soft'); + else + setWrap('off'); +} + +function resizeInputs() { + var vp = tinyMCEPopup.dom.getViewPort(window), el; + + el = document.getElementById('htmlSource'); + + if (el) { + el.style.width = (vp.w - 20) + 'px'; + el.style.height = (vp.h - 65) + 'px'; + } +} diff --git a/web/js/globals/tinymce/themes/advanced/langs/en.js b/web/js/globals/tinymce/themes/advanced/langs/en.js new file mode 100644 index 0000000..69694b1 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/langs/en.js @@ -0,0 +1,62 @@ +tinyMCE.addI18n('en.advanced',{ +style_select:"Styles", +font_size:"Font size", +fontdefault:"Font family", +block:"Format", +paragraph:"Paragraph", +div:"Div", +address:"Address", +pre:"Preformatted", +h1:"Heading 1", +h2:"Heading 2", +h3:"Heading 3", +h4:"Heading 4", +h5:"Heading 5", +h6:"Heading 6", +blockquote:"Blockquote", +code:"Code", +samp:"Code sample", +dt:"Definition term ", +dd:"Definition description", +bold_desc:"Bold (Ctrl+B)", +italic_desc:"Italic (Ctrl+I)", +underline_desc:"Underline (Ctrl+U)", +striketrough_desc:"Strikethrough", +justifyleft_desc:"Align left", +justifycenter_desc:"Align center", +justifyright_desc:"Align right", +justifyfull_desc:"Align full", +bullist_desc:"Unordered list", +numlist_desc:"Ordered list", +outdent_desc:"Outdent", +indent_desc:"Indent", +undo_desc:"Undo (Ctrl+Z)", +redo_desc:"Redo (Ctrl+Y)", +link_desc:"Insert/edit link", +unlink_desc:"Unlink", +image_desc:"Insert/edit image", +cleanup_desc:"Cleanup messy code", +code_desc:"Edit HTML Source", +sub_desc:"Subscript", +sup_desc:"Superscript", +hr_desc:"Insert horizontal ruler", +removeformat_desc:"Remove formatting", +custom1_desc:"Your custom description here", +forecolor_desc:"Select text color", +backcolor_desc:"Select background color", +charmap_desc:"Insert custom character", +visualaid_desc:"Toggle guidelines/invisible elements", +anchor_desc:"Insert/edit anchor", +cut_desc:"Cut", +copy_desc:"Copy", +paste_desc:"Paste", +image_props_desc:"Image properties", +newdocument_desc:"New document", +help_desc:"Help", +blockquote_desc:"Blockquote", +clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\r\nDo you want more information about this issue?", +path:"Path", +newdocument:"Are you sure you want clear all contents?", +toolbar_focus:"Jump to tool buttons - Alt+Q, Jump to editor - Alt-Z, Jump to element path - Alt-X", +more_colors:"More colors" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/themes/advanced/langs/en_dlg.js b/web/js/globals/tinymce/themes/advanced/langs/en_dlg.js new file mode 100644 index 0000000..9d124d7 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/langs/en_dlg.js @@ -0,0 +1,51 @@ +tinyMCE.addI18n('en.advanced_dlg',{ +about_title:"About TinyMCE", +about_general:"About", +about_help:"Help", +about_license:"License", +about_plugins:"Plugins", +about_plugin:"Plugin", +about_author:"Author", +about_version:"Version", +about_loaded:"Loaded plugins", +anchor_title:"Insert/edit anchor", +anchor_name:"Anchor name", +code_title:"HTML Source Editor", +code_wordwrap:"Word wrap", +colorpicker_title:"Select a color", +colorpicker_picker_tab:"Picker", +colorpicker_picker_title:"Color picker", +colorpicker_palette_tab:"Palette", +colorpicker_palette_title:"Palette colors", +colorpicker_named_tab:"Named", +colorpicker_named_title:"Named colors", +colorpicker_color:"Color:", +colorpicker_name:"Name:", +charmap_title:"Select custom character", +image_title:"Insert/edit image", +image_src:"Image URL", +image_alt:"Image description", +image_list:"Image list", +image_border:"Border", +image_dimensions:"Dimensions", +image_vspace:"Vertical space", +image_hspace:"Horizontal space", +image_align:"Alignment", +image_align_baseline:"Baseline", +image_align_top:"Top", +image_align_middle:"Middle", +image_align_bottom:"Bottom", +image_align_texttop:"Text top", +image_align_textbottom:"Text bottom", +image_align_left:"Left", +image_align_right:"Right", +link_title:"Insert/edit link", +link_url:"Link URL", +link_target:"Target", +link_target_same:"Open link in the same window", +link_target_blank:"Open link in a new window", +link_titlefield:"Title", +link_is_email:"The URL you entered seems to be an email address, do you want to add the required mailto: prefix?", +link_is_external:"The URL you entered seems to external link, do you want to add the required http:// prefix?", +link_list:"Link list" +}); \ No newline at end of file diff --git a/web/js/globals/tinymce/themes/advanced/link.htm b/web/js/globals/tinymce/themes/advanced/link.htm new file mode 100644 index 0000000..7565b9a --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/link.htm @@ -0,0 +1,58 @@ + + + + {#advanced_dlg.link_title} + + + + + + + +
      + + +
      +
      + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
       
      +
      +
      + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/themes/advanced/skins/default/content.css b/web/js/globals/tinymce/themes/advanced/skins/default/content.css new file mode 100644 index 0000000..9fba043 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/skins/default/content.css @@ -0,0 +1,36 @@ +body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;} +body {background:#FFF;} +body.mceForceColors {background:#FFF; color:#000;} +h1 {font-size: 2em} +h2 {font-size: 1.5em} +h3 {font-size: 1.17em} +h4 {font-size: 1em} +h5 {font-size: .83em} +h6 {font-size: .75em} +.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;} +a.mceItemAnchor {display:inline-block; width:11px !important; height:11px !important; background:url(img/items.gif) no-repeat 0 0;} +span.mceItemNbsp {background: #DDD} +td.mceSelected, th.mceSelected {background-color:#3399ff !important} +img {border:0;} +table {cursor:default} +table td, table th {cursor:text} +ins {border-bottom:1px solid green; text-decoration: none; color:green} +del {color:red; text-decoration:line-through} +cite {border-bottom:1px dashed blue} +acronym {border-bottom:1px dotted #CCC; cursor:help} +abbr {border-bottom:1px dashed #CCC; cursor:help} + +/* IE */ +* html body { +scrollbar-3dlight-color:#F0F0EE; +scrollbar-arrow-color:#676662; +scrollbar-base-color:#F0F0EE; +scrollbar-darkshadow-color:#DDD; +scrollbar-face-color:#E0E0DD; +scrollbar-highlight-color:#F0F0EE; +scrollbar-shadow-color:#F0F0EE; +scrollbar-track-color:#F5F5F5; +} + +img:-moz-broken {-moz-force-broken-image-icon:1; width:24px; height:24px} +font[face=mceinline] {font-family:inherit !important} diff --git a/web/js/globals/tinymce/themes/advanced/skins/default/dialog.css b/web/js/globals/tinymce/themes/advanced/skins/default/dialog.css new file mode 100644 index 0000000..f012226 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/skins/default/dialog.css @@ -0,0 +1,117 @@ +/* Generic */ +body { +font-family:Verdana, Arial, Helvetica, sans-serif; font-size:11px; +scrollbar-3dlight-color:#F0F0EE; +scrollbar-arrow-color:#676662; +scrollbar-base-color:#F0F0EE; +scrollbar-darkshadow-color:#DDDDDD; +scrollbar-face-color:#E0E0DD; +scrollbar-highlight-color:#F0F0EE; +scrollbar-shadow-color:#F0F0EE; +scrollbar-track-color:#F5F5F5; +background:#F0F0EE; +padding:0; +margin:8px 8px 0 8px; +} + +html {background:#F0F0EE;} +td {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} +textarea {resize:none;outline:none;} +a:link, a:visited {color:black;} +a:hover {color:#2B6FB6;} +.nowrap {white-space: nowrap} + +/* Forms */ +fieldset {margin:0; padding:4px; border:1px solid #919B9C; font-family:Verdana, Arial; font-size:10px;} +legend {color:#2B6FB6; font-weight:bold;} +label.msg {display:none;} +label.invalid {color:#EE0000; display:inline;} +input.invalid {border:1px solid #EE0000;} +input {background:#FFF; border:1px solid #CCC;} +input, select, textarea {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} +input, select, textarea {border:1px solid #808080;} +input.radio {border:1px none #000000; background:transparent; vertical-align:middle;} +input.checkbox {border:1px none #000000; background:transparent; vertical-align:middle;} +.input_noborder {border:0;} + +/* Buttons */ +#insert, #cancel, input.button, .updateButton { +border:0; margin:0; padding:0; +font-weight:bold; +width:94px; height:26px; +background:url(img/buttons.png) 0 -26px; +cursor:pointer; +padding-bottom:2px; +float:left; +} + +#insert {background:url(img/buttons.png) 0 -52px} +#cancel {background:url(img/buttons.png) 0 0; float:right} + +/* Browse */ +a.pickcolor, a.browse {text-decoration:none} +a.browse span {display:block; width:20px; height:18px; background:url(../../img/icons.gif) -860px 0; border:1px solid #FFF; margin-left:1px;} +.mceOldBoxModel a.browse span {width:22px; height:20px;} +a.browse:hover span {border:1px solid #0A246A; background-color:#B2BBD0;} +a.browse span.disabled {border:1px solid white; opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +a.browse:hover span.disabled {border:1px solid white; background-color:transparent;} +a.pickcolor span {display:block; width:20px; height:16px; background:url(../../img/icons.gif) -840px 0; margin-left:2px;} +.mceOldBoxModel a.pickcolor span {width:21px; height:17px;} +a.pickcolor:hover span {background-color:#B2BBD0;} +a.pickcolor:hover span.disabled {} + +/* Charmap */ +table.charmap {border:1px solid #AAA; text-align:center} +td.charmap, #charmap a {width:18px; height:18px; color:#000; border:1px solid #AAA; text-align:center; font-size:12px; vertical-align:middle; line-height: 18px;} +#charmap a {display:block; color:#000; text-decoration:none; border:0} +#charmap a:hover {background:#CCC;color:#2B6FB6} +#charmap #codeN {font-size:10px; font-family:Arial,Helvetica,sans-serif; text-align:center} +#charmap #codeV {font-size:40px; height:80px; border:1px solid #AAA; text-align:center} + +/* Source */ +.wordWrapCode {vertical-align:middle; border:1px none #000000; background:transparent;} +.mceActionPanel {margin-top:5px;} + +/* Tabs classes */ +.tabs {width:100%; height:18px; line-height:normal; background:url(img/tabs.gif) repeat-x 0 -72px;} +.tabs ul {margin:0; padding:0; list-style:none;} +.tabs li {float:left; background:url(img/tabs.gif) no-repeat 0 0; margin:0 2px 0 0; padding:0 0 0 10px; line-height:17px; height:18px; display:block;} +.tabs li.current {background:url(img/tabs.gif) no-repeat 0 -18px; margin-right:2px;} +.tabs span {float:left; display:block; background:url(img/tabs.gif) no-repeat right -36px; padding:0px 10px 0 0;} +.tabs .current span {background:url(img/tabs.gif) no-repeat right -54px;} +.tabs a {text-decoration:none; font-family:Verdana, Arial; font-size:10px;} +.tabs a:link, .tabs a:visited, .tabs a:hover {color:black;} + +/* Panels */ +.panel_wrapper div.panel {display:none;} +.panel_wrapper div.current {display:block; width:100%; height:300px; overflow:visible;} +.panel_wrapper {border:1px solid #919B9C; border-top:0px; padding:10px; padding-top:5px; clear:both; background:white;} + +/* Columns */ +.column {float:left;} +.properties {width:100%;} +.properties .column1 {} +.properties .column2 {text-align:left;} + +/* Titles */ +h1, h2, h3, h4 {color:#2B6FB6; margin:0; padding:0; padding-top:5px;} +h3 {font-size:14px;} +.title {font-size:12px; font-weight:bold; color:#2B6FB6;} + +/* Dialog specific */ +#link .panel_wrapper, #link div.current {height:125px;} +#image .panel_wrapper, #image div.current {height:200px;} +#plugintable thead {font-weight:bold; background:#DDD;} +#plugintable, #about #plugintable td {border:1px solid #919B9C;} +#plugintable {width:96%; margin-top:10px;} +#pluginscontainer {height:290px; overflow:auto;} +#colorpicker #preview {float:right; width:50px; height:14px;line-height:1px; border:1px solid black; margin-left:5px;} +#colorpicker #colors {float:left; border:1px solid gray; cursor:crosshair;} +#colorpicker #light {border:1px solid gray; margin-left:5px; float:left;width:15px; height:150px; cursor:crosshair;} +#colorpicker #light div {overflow:hidden;} +#colorpicker #previewblock {float:right; padding-left:10px; height:20px;} +#colorpicker .panel_wrapper div.current {height:175px;} +#colorpicker #namedcolors {width:150px;} +#colorpicker #namedcolors a {display:block; float:left; width:10px; height:10px; margin:1px 1px 0 0; overflow:hidden;} +#colorpicker #colornamecontainer {margin-top:5px;} +#colorpicker #picker_panel fieldset {margin:auto;width:325px;} diff --git a/web/js/globals/tinymce/themes/advanced/skins/default/img/buttons.png b/web/js/globals/tinymce/themes/advanced/skins/default/img/buttons.png new file mode 100644 index 0000000000000000000000000000000000000000..7dd58418ba7cfe58ae7efdf174e0b223fe3aa6a0 GIT binary patch literal 3274 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 SLTQbO*E^TJI;F+RU=09Vu@yA{ literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/themes/advanced/skins/default/img/menu_check.gif b/web/js/globals/tinymce/themes/advanced/skins/default/img/menu_check.gif new file mode 100644 index 0000000000000000000000000000000000000000..adfdddccd7cac62a17d68873fa53c248bff8351a GIT binary patch literal 70 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 R!Sp}^Mt9BhYjvGt{|88cricIl literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/themes/advanced/skins/o2k7/img/button_bg_black.png b/web/js/globals/tinymce/themes/advanced/skins/o2k7/img/button_bg_black.png new file mode 100644 index 0000000000000000000000000000000000000000..8996c7493e8a58c9c40845cbe8abdc3e6730716d GIT binary patch literal 3736 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 M07*qoM6N<$f@g#ZbN~PV literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui.css b/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui.css new file mode 100644 index 0000000..a625397 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui.css @@ -0,0 +1,215 @@ +/* Reset */ +.o2k7Skin table, .o2k7Skin tbody, .o2k7Skin a, .o2k7Skin img, .o2k7Skin tr, .o2k7Skin div, .o2k7Skin td, .o2k7Skin iframe, .o2k7Skin span, .o2k7Skin *, .o2k7Skin .mceText {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000; vertical-align:baseline; width:auto; border-collapse:separate; text-align:left} +.o2k7Skin a:hover, .o2k7Skin a:link, .o2k7Skin a:visited, .o2k7Skin a:active {text-decoration:none; font-weight:normal; cursor:default; color:#000} +.o2k7Skin table td {vertical-align:middle} + +/* Containers */ +.o2k7Skin table {background:#E5EFFD} +.o2k7Skin iframe {display:block; background:#FFF} +.o2k7Skin .mceToolbar {height:26px} + +/* External */ +.o2k7Skin .mceExternalToolbar {position:absolute; border:1px solid #ABC6DD; border-bottom:0; display:none} +.o2k7Skin .mceExternalToolbar td.mceToolbar {padding-right:13px;} +.o2k7Skin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px; background:url(../../img/icons.gif) -820px 0} + +/* Layout */ +.o2k7Skin table.mceLayout {border:0; border-left:1px solid #ABC6DD; border-right:1px solid #ABC6DD} +.o2k7Skin table.mceLayout tr.mceFirst td {border-top:1px solid #ABC6DD} +.o2k7Skin table.mceLayout tr.mceLast td {border-bottom:1px solid #ABC6DD} +.o2k7Skin table.mceToolbar, .o2k7Skin tr.mceFirst .mceToolbar tr td, .o2k7Skin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0} +.o2k7Skin .mceIframeContainer {border-top:1px solid #ABC6DD; border-bottom:1px solid #ABC6DD} +.o2k7Skin .mceStatusbar {display:block; font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:9pt; line-height:16px; overflow:visible; color:#000; height:20px} +.o2k7Skin .mceStatusbar div {float:left; padding:2px} +.o2k7Skin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/icons.gif) -800px 0; width:20px; height:20px; cursor:se-resize; outline:0} +.o2k7Skin .mceStatusbar a:hover {text-decoration:underline} +.o2k7Skin table.mceToolbar {margin-left:3px} +.o2k7Skin .mceToolbar .mceToolbarStart span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px; margin-left:3px;} +.o2k7Skin .mceToolbar td.mceFirst span {margin:0} +.o2k7Skin .mceToolbar .mceToolbarEnd span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px} +.o2k7Skin .mceToolbar .mceToolbarEndListBox span, .o2k7Skin .mceToolbar .mceToolbarStartListBox span {display:none} +.o2k7Skin span.mceIcon, .o2k7Skin img.mceIcon {display:block; width:20px; height:20px} +.o2k7Skin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px} +.o2k7Skin td.mceCenter {text-align:center;} +.o2k7Skin td.mceCenter table {margin:0 auto; text-align:left;} +.o2k7Skin td.mceRight table {margin:0 0 0 auto;} + +/* Button */ +.o2k7Skin .mceButton {display:block; background:url(img/button_bg.png); width:22px; height:22px} +.o2k7Skin a.mceButton span, .o2k7Skin a.mceButton img {margin-left:1px} +.o2k7Skin .mceOldBoxModel a.mceButton span, .o2k7Skin .mceOldBoxModel a.mceButton img {margin:0 0 0 1px} +.o2k7Skin a.mceButtonEnabled:hover {background-color:#B2BBD0; background-position:0 -22px} +.o2k7Skin a.mceButtonActive, .o2k7Skin a.mceButtonSelected {background-position:0 -44px} +.o2k7Skin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +.o2k7Skin .mceButtonLabeled {width:auto} +.o2k7Skin .mceButtonLabeled span.mceIcon {float:left} +.o2k7Skin span.mceButtonLabel {display:block; font-size:10px; padding:4px 6px 0 22px; font-family:Tahoma,Verdana,Arial,Helvetica} +.o2k7Skin .mceButtonDisabled .mceButtonLabel {color:#888} + +/* Separator */ +.o2k7Skin .mceSeparator {display:block; background:url(img/button_bg.png) -22px 0; width:5px; height:22px} + +/* ListBox */ +.o2k7Skin .mceListBox {margin-left:3px} +.o2k7Skin .mceListBox, .o2k7Skin .mceListBox a {display:block} +.o2k7Skin .mceListBox .mceText {padding-left:4px; text-align:left; width:70px; border:1px solid #b3c7e1; border-right:0; background:#eaf2fb; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden} +.o2k7Skin .mceListBox .mceOpen {width:14px; height:22px; background:url(img/button_bg.png) -66px 0} +.o2k7Skin table.mceListBoxEnabled:hover .mceText, .o2k7Skin .mceListBoxHover .mceText, .o2k7Skin .mceListBoxSelected .mceText {background:#FFF} +.o2k7Skin table.mceListBoxEnabled:hover .mceOpen, .o2k7Skin .mceListBoxHover .mceOpen, .o2k7Skin .mceListBoxSelected .mceOpen {background-position:-66px -22px} +.o2k7Skin .mceListBoxDisabled .mceText {color:gray} +.o2k7Skin .mceListBoxMenu {overflow:auto; overflow-x:hidden} +.o2k7Skin .mceOldBoxModel .mceListBox .mceText {height:22px} +.o2k7Skin select.mceListBox {font-family:Tahoma,Verdana,Arial,Helvetica; font-size:12px; border:1px solid #b3c7e1; background:#FFF;} + +/* SplitButton */ +.o2k7Skin .mceSplitButton, .o2k7Skin .mceSplitButton a, .o2k7Skin .mceSplitButton span {display:block; height:22px} +.o2k7Skin .mceSplitButton {background:url(img/button_bg.png)} +.o2k7Skin .mceSplitButton a.mceAction {width:22px} +.o2k7Skin .mceSplitButton span.mceAction {width:22px; background-image:url(../../img/icons.gif)} +.o2k7Skin .mceSplitButton a.mceOpen {width:10px; background:url(img/button_bg.png) -44px 0} +.o2k7Skin .mceSplitButton span.mceOpen {display:none} +.o2k7Skin table.mceSplitButtonEnabled:hover a.mceAction, .o2k7Skin .mceSplitButtonHover a.mceAction, .o2k7Skin .mceSplitButtonSelected {background:url(img/button_bg.png) 0 -22px} +.o2k7Skin table.mceSplitButtonEnabled:hover a.mceOpen, .o2k7Skin .mceSplitButtonHover a.mceOpen, .o2k7Skin .mceSplitButtonSelected a.mceOpen {background-position:-44px -44px} +.o2k7Skin .mceSplitButtonDisabled .mceAction {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +.o2k7Skin .mceSplitButtonActive {background-position:0 -44px} + +/* ColorSplitButton */ +.o2k7Skin div.mceColorSplitMenu table {background:#FFF; border:1px solid gray} +.o2k7Skin .mceColorSplitMenu td {padding:2px} +.o2k7Skin .mceColorSplitMenu a {display:block; width:9px; height:9px; overflow:hidden; border:1px solid #808080} +.o2k7Skin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px} +.o2k7Skin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF} +.o2k7Skin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2} +.o2k7Skin a.mceMoreColors:hover {border:1px solid #0A246A} +.o2k7Skin .mceColorPreview {margin-left:2px; width:16px; height:4px; overflow:hidden; background:#9a9b9a;overflow:hidden} +.o2k7Skin .mce_forecolor span.mceAction, .o2k7Skin .mce_backcolor span.mceAction {height:15px;overflow:hidden} + +/* Menu */ +.o2k7Skin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid #ABC6DD} +.o2k7Skin .mceNoIcons span.mceIcon {width:0;} +.o2k7Skin .mceNoIcons a .mceText {padding-left:10px} +.o2k7Skin .mceMenu table {background:#FFF} +.o2k7Skin .mceMenu a, .o2k7Skin .mceMenu span, .o2k7Skin .mceMenu {display:block} +.o2k7Skin .mceMenu td {height:20px} +.o2k7Skin .mceMenu a {position:relative;padding:3px 0 4px 0} +.o2k7Skin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block} +.o2k7Skin .mceMenu span.mceText, .o2k7Skin .mceMenu .mcePreview {font-size:11px} +.o2k7Skin .mceMenu pre.mceText {font-family:Monospace} +.o2k7Skin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:22px;} +.o2k7Skin .mceMenu .mceMenuItemEnabled a:hover, .o2k7Skin .mceMenu .mceMenuItemActive {background-color:#dbecf3} +.o2k7Skin td.mceMenuItemSeparator {background:#DDD; height:1px} +.o2k7Skin .mceMenuItemTitle a {border:0; background:#E5EFFD; border-bottom:1px solid #ABC6DD} +.o2k7Skin .mceMenuItemTitle span.mceText {color:#000; font-weight:bold; padding-left:4px} +.o2k7Skin .mceMenuItemDisabled .mceText {color:#888} +.o2k7Skin .mceMenuItemSelected .mceIcon {background:url(../default/img/menu_check.gif)} +.o2k7Skin .mceNoIcons .mceMenuItemSelected a {background:url(../default/img/menu_arrow.gif) no-repeat -6px center} +.o2k7Skin .mceMenu span.mceMenuLine {display:none} +.o2k7Skin .mceMenuItemSub a {background:url(../default/img/menu_arrow.gif) no-repeat top right;} + +/* Progress,Resize */ +.o2k7Skin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=50); background:#FFF} +.o2k7Skin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(../default/img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px} + +/* Formats */ +.o2k7Skin .mce_formatPreview a {font-size:10px} +.o2k7Skin .mce_p span.mceText {} +.o2k7Skin .mce_address span.mceText {font-style:italic} +.o2k7Skin .mce_pre span.mceText {font-family:monospace} +.o2k7Skin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em} +.o2k7Skin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em} +.o2k7Skin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em} +.o2k7Skin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em} +.o2k7Skin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em} +.o2k7Skin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em} + +/* Theme */ +.o2k7Skin span.mce_bold {background-position:0 0} +.o2k7Skin span.mce_italic {background-position:-60px 0} +.o2k7Skin span.mce_underline {background-position:-140px 0} +.o2k7Skin span.mce_strikethrough {background-position:-120px 0} +.o2k7Skin span.mce_undo {background-position:-160px 0} +.o2k7Skin span.mce_redo {background-position:-100px 0} +.o2k7Skin span.mce_cleanup {background-position:-40px 0} +.o2k7Skin span.mce_bullist {background-position:-20px 0} +.o2k7Skin span.mce_numlist {background-position:-80px 0} +.o2k7Skin span.mce_justifyleft {background-position:-460px 0} +.o2k7Skin span.mce_justifyright {background-position:-480px 0} +.o2k7Skin span.mce_justifycenter {background-position:-420px 0} +.o2k7Skin span.mce_justifyfull {background-position:-440px 0} +.o2k7Skin span.mce_anchor {background-position:-200px 0} +.o2k7Skin span.mce_indent {background-position:-400px 0} +.o2k7Skin span.mce_outdent {background-position:-540px 0} +.o2k7Skin span.mce_link {background-position:-500px 0} +.o2k7Skin span.mce_unlink {background-position:-640px 0} +.o2k7Skin span.mce_sub {background-position:-600px 0} +.o2k7Skin span.mce_sup {background-position:-620px 0} +.o2k7Skin span.mce_removeformat {background-position:-580px 0} +.o2k7Skin span.mce_newdocument {background-position:-520px 0} +.o2k7Skin span.mce_image {background-position:-380px 0} +.o2k7Skin span.mce_help {background-position:-340px 0} +.o2k7Skin span.mce_code {background-position:-260px 0} +.o2k7Skin span.mce_hr {background-position:-360px 0} +.o2k7Skin span.mce_visualaid {background-position:-660px 0} +.o2k7Skin span.mce_charmap {background-position:-240px 0} +.o2k7Skin span.mce_paste {background-position:-560px 0} +.o2k7Skin span.mce_copy {background-position:-700px 0} +.o2k7Skin span.mce_cut {background-position:-680px 0} +.o2k7Skin span.mce_blockquote {background-position:-220px 0} +.o2k7Skin .mce_forecolor span.mceAction {background-position:-720px 0} +.o2k7Skin .mce_backcolor span.mceAction {background-position:-760px 0} +.o2k7Skin span.mce_forecolorpicker {background-position:-720px 0} +.o2k7Skin span.mce_backcolorpicker {background-position:-760px 0} + +/* Plugins */ +.o2k7Skin span.mce_advhr {background-position:-0px -20px} +.o2k7Skin span.mce_ltr {background-position:-20px -20px} +.o2k7Skin span.mce_rtl {background-position:-40px -20px} +.o2k7Skin span.mce_emotions {background-position:-60px -20px} +.o2k7Skin span.mce_fullpage {background-position:-80px -20px} +.o2k7Skin span.mce_fullscreen {background-position:-100px -20px} +.o2k7Skin span.mce_iespell {background-position:-120px -20px} +.o2k7Skin span.mce_insertdate {background-position:-140px -20px} +.o2k7Skin span.mce_inserttime {background-position:-160px -20px} +.o2k7Skin span.mce_absolute {background-position:-180px -20px} +.o2k7Skin span.mce_backward {background-position:-200px -20px} +.o2k7Skin span.mce_forward {background-position:-220px -20px} +.o2k7Skin span.mce_insert_layer {background-position:-240px -20px} +.o2k7Skin span.mce_insertlayer {background-position:-260px -20px} +.o2k7Skin span.mce_movebackward {background-position:-280px -20px} +.o2k7Skin span.mce_moveforward {background-position:-300px -20px} +.o2k7Skin span.mce_media {background-position:-320px -20px} +.o2k7Skin span.mce_nonbreaking {background-position:-340px -20px} +.o2k7Skin span.mce_pastetext {background-position:-360px -20px} +.o2k7Skin span.mce_pasteword {background-position:-380px -20px} +.o2k7Skin span.mce_selectall {background-position:-400px -20px} +.o2k7Skin span.mce_preview {background-position:-420px -20px} +.o2k7Skin span.mce_print {background-position:-440px -20px} +.o2k7Skin span.mce_cancel {background-position:-460px -20px} +.o2k7Skin span.mce_save {background-position:-480px -20px} +.o2k7Skin span.mce_replace {background-position:-500px -20px} +.o2k7Skin span.mce_search {background-position:-520px -20px} +.o2k7Skin span.mce_styleprops {background-position:-560px -20px} +.o2k7Skin span.mce_table {background-position:-580px -20px} +.o2k7Skin span.mce_cell_props {background-position:-600px -20px} +.o2k7Skin span.mce_delete_table {background-position:-620px -20px} +.o2k7Skin span.mce_delete_col {background-position:-640px -20px} +.o2k7Skin span.mce_delete_row {background-position:-660px -20px} +.o2k7Skin span.mce_col_after {background-position:-680px -20px} +.o2k7Skin span.mce_col_before {background-position:-700px -20px} +.o2k7Skin span.mce_row_after {background-position:-720px -20px} +.o2k7Skin span.mce_row_before {background-position:-740px -20px} +.o2k7Skin span.mce_merge_cells {background-position:-760px -20px} +.o2k7Skin span.mce_table_props {background-position:-980px -20px} +.o2k7Skin span.mce_row_props {background-position:-780px -20px} +.o2k7Skin span.mce_split_cells {background-position:-800px -20px} +.o2k7Skin span.mce_template {background-position:-820px -20px} +.o2k7Skin span.mce_visualchars {background-position:-840px -20px} +.o2k7Skin span.mce_abbr {background-position:-860px -20px} +.o2k7Skin span.mce_acronym {background-position:-880px -20px} +.o2k7Skin span.mce_attribs {background-position:-900px -20px} +.o2k7Skin span.mce_cite {background-position:-920px -20px} +.o2k7Skin span.mce_del {background-position:-940px -20px} +.o2k7Skin span.mce_ins {background-position:-960px -20px} +.o2k7Skin span.mce_pagebreak {background-position:0 -40px} +.o2k7Skin span.mce_restoredraft {background-position:-20px -40px} +.o2k7Skin span.mce_spellchecker {background-position:-540px -20px} diff --git a/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_black.css b/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_black.css new file mode 100644 index 0000000..153f0c3 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_black.css @@ -0,0 +1,8 @@ +/* Black */ +.o2k7SkinBlack .mceToolbar .mceToolbarStart span, .o2k7SkinBlack .mceToolbar .mceToolbarEnd span, .o2k7SkinBlack .mceButton, .o2k7SkinBlack .mceSplitButton, .o2k7SkinBlack .mceSeparator, .o2k7SkinBlack .mceSplitButton a.mceOpen, .o2k7SkinBlack .mceListBox a.mceOpen {background-image:url(img/button_bg_black.png)} +.o2k7SkinBlack table, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack .mceMenuItemTitle span.mceText, .o2k7SkinBlack .mceStatusbar div, .o2k7SkinBlack .mceStatusbar span, .o2k7SkinBlack .mceStatusbar a {background:#535353; color:#FFF} +.o2k7SkinBlack table.mceListBoxEnabled .mceText, o2k7SkinBlack .mceListBox .mceText {background:#FFF; border:1px solid #CBCFD4; border-bottom-color:#989FA9; border-right:0} +.o2k7SkinBlack table.mceListBoxEnabled:hover .mceText, .o2k7SkinBlack .mceListBoxHover .mceText, .o2k7SkinBlack .mceListBoxSelected .mceText {background:#FFF; border:1px solid #FFBD69; border-right:0} +.o2k7SkinBlack .mceExternalToolbar, .o2k7SkinBlack .mceListBox .mceText, .o2k7SkinBlack div.mceMenu, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceFirst td, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceLast td, .o2k7SkinBlack .mceIframeContainer {border-color: #535353;} +.o2k7SkinBlack table.mceSplitButtonEnabled:hover a.mceAction, .o2k7SkinBlack .mceSplitButtonHover a.mceAction, .o2k7SkinBlack .mceSplitButtonSelected {background-image:url(img/button_bg_black.png)} +.o2k7SkinBlack .mceMenu .mceMenuItemEnabled a:hover, .o2k7SkinBlack .mceMenu .mceMenuItemActive {background-color:#FFE7A1} \ No newline at end of file diff --git a/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_silver.css b/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_silver.css new file mode 100644 index 0000000..7fe3b45 --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/skins/o2k7/ui_silver.css @@ -0,0 +1,5 @@ +/* Silver */ +.o2k7SkinSilver .mceToolbar .mceToolbarStart span, .o2k7SkinSilver .mceButton, .o2k7SkinSilver .mceSplitButton, .o2k7SkinSilver .mceSeparator, .o2k7SkinSilver .mceSplitButton a.mceOpen, .o2k7SkinSilver .mceListBox a.mceOpen {background-image:url(img/button_bg_silver.png)} +.o2k7SkinSilver table, .o2k7SkinSilver .mceMenuItemTitle a {background:#eee} +.o2k7SkinSilver .mceListBox .mceText {background:#FFF} +.o2k7SkinSilver .mceExternalToolbar, .o2k7SkinSilver .mceListBox .mceText, .o2k7SkinSilver div.mceMenu, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceFirst td, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceLast td, .o2k7SkinSilver .mceIframeContainer {border-color: #bbb} diff --git a/web/js/globals/tinymce/themes/advanced/source_editor.htm b/web/js/globals/tinymce/themes/advanced/source_editor.htm new file mode 100644 index 0000000..5957bbd --- /dev/null +++ b/web/js/globals/tinymce/themes/advanced/source_editor.htm @@ -0,0 +1,25 @@ + + + {#advanced_dlg.code_title} + + + + +
      +
      {#advanced_dlg.code_title}
      + +
      + +
      + +
      + + + +
      + + +
      +
      + + diff --git a/web/js/globals/tinymce/themes/simple/editor_template.js b/web/js/globals/tinymce/themes/simple/editor_template.js new file mode 100644 index 0000000..ed89abc --- /dev/null +++ b/web/js/globals/tinymce/themes/simple/editor_template.js @@ -0,0 +1 @@ +(function(){var a=tinymce.DOM;tinymce.ThemeManager.requireLangPack("simple");tinymce.create("tinymce.themes.SimpleTheme",{init:function(c,d){var e=this,b=["Bold","Italic","Underline","Strikethrough","InsertUnorderedList","InsertOrderedList"],f=c.settings;e.editor=c;c.onInit.add(function(){c.onNodeChange.add(function(h,g){tinymce.each(b,function(i){g.get(i.toLowerCase()).setActive(h.queryCommandState(i))})});c.dom.loadCSS(d+"/skins/"+f.skin+"/content.css")});a.loadCSS((f.editor_css?c.documentBaseURI.toAbsolute(f.editor_css):"")||d+"/skins/"+f.skin+"/ui.css")},renderUI:function(h){var e=this,i=h.targetNode,b,c,d=e.editor,f=d.controlManager,g;i=a.insertAfter(a.create("span",{id:d.id+"_container","class":"mceEditor "+d.settings.skin+"SimpleSkin"}),i);i=g=a.add(i,"table",{cellPadding:0,cellSpacing:0,"class":"mceLayout"});i=c=a.add(i,"tbody");i=a.add(c,"tr");i=b=a.add(a.add(i,"td"),"div",{"class":"mceIframeContainer"});i=a.add(a.add(c,"tr",{"class":"last"}),"td",{"class":"mceToolbar mceLast",align:"center"});c=e.toolbar=f.createToolbar("tools1");c.add(f.createButton("bold",{title:"simple.bold_desc",cmd:"Bold"}));c.add(f.createButton("italic",{title:"simple.italic_desc",cmd:"Italic"}));c.add(f.createButton("underline",{title:"simple.underline_desc",cmd:"Underline"}));c.add(f.createButton("strikethrough",{title:"simple.striketrough_desc",cmd:"Strikethrough"}));c.add(f.createSeparator());c.add(f.createButton("undo",{title:"simple.undo_desc",cmd:"Undo"}));c.add(f.createButton("redo",{title:"simple.redo_desc",cmd:"Redo"}));c.add(f.createSeparator());c.add(f.createButton("cleanup",{title:"simple.cleanup_desc",cmd:"mceCleanup"}));c.add(f.createSeparator());c.add(f.createButton("insertunorderedlist",{title:"simple.bullist_desc",cmd:"InsertUnorderedList"}));c.add(f.createButton("insertorderedlist",{title:"simple.numlist_desc",cmd:"InsertOrderedList"}));c.renderTo(i);return{iframeContainer:b,editorContainer:d.id+"_container",sizeContainer:g,deltaHeight:-20}},getInfo:function(){return{longname:"Simple theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.ThemeManager.add("simple",tinymce.themes.SimpleTheme)})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/themes/simple/editor_template_src.js b/web/js/globals/tinymce/themes/simple/editor_template_src.js new file mode 100644 index 0000000..4b862d4 --- /dev/null +++ b/web/js/globals/tinymce/themes/simple/editor_template_src.js @@ -0,0 +1,85 @@ +/** + * editor_template_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM; + + // Tell it to load theme specific language pack(s) + tinymce.ThemeManager.requireLangPack('simple'); + + tinymce.create('tinymce.themes.SimpleTheme', { + init : function(ed, url) { + var t = this, states = ['Bold', 'Italic', 'Underline', 'Strikethrough', 'InsertUnorderedList', 'InsertOrderedList'], s = ed.settings; + + t.editor = ed; + + ed.onInit.add(function() { + ed.onNodeChange.add(function(ed, cm) { + tinymce.each(states, function(c) { + cm.get(c.toLowerCase()).setActive(ed.queryCommandState(c)); + }); + }); + + ed.dom.loadCSS(url + "/skins/" + s.skin + "/content.css"); + }); + + DOM.loadCSS((s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : '') || url + "/skins/" + s.skin + "/ui.css"); + }, + + renderUI : function(o) { + var t = this, n = o.targetNode, ic, tb, ed = t.editor, cf = ed.controlManager, sc; + + n = DOM.insertAfter(DOM.create('span', {id : ed.id + '_container', 'class' : 'mceEditor ' + ed.settings.skin + 'SimpleSkin'}), n); + n = sc = DOM.add(n, 'table', {cellPadding : 0, cellSpacing : 0, 'class' : 'mceLayout'}); + n = tb = DOM.add(n, 'tbody'); + + // Create iframe container + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(DOM.add(n, 'td'), 'div', {'class' : 'mceIframeContainer'}); + + // Create toolbar container + n = DOM.add(DOM.add(tb, 'tr', {'class' : 'last'}), 'td', {'class' : 'mceToolbar mceLast', align : 'center'}); + + // Create toolbar + tb = t.toolbar = cf.createToolbar("tools1"); + tb.add(cf.createButton('bold', {title : 'simple.bold_desc', cmd : 'Bold'})); + tb.add(cf.createButton('italic', {title : 'simple.italic_desc', cmd : 'Italic'})); + tb.add(cf.createButton('underline', {title : 'simple.underline_desc', cmd : 'Underline'})); + tb.add(cf.createButton('strikethrough', {title : 'simple.striketrough_desc', cmd : 'Strikethrough'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('undo', {title : 'simple.undo_desc', cmd : 'Undo'})); + tb.add(cf.createButton('redo', {title : 'simple.redo_desc', cmd : 'Redo'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('cleanup', {title : 'simple.cleanup_desc', cmd : 'mceCleanup'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('insertunorderedlist', {title : 'simple.bullist_desc', cmd : 'InsertUnorderedList'})); + tb.add(cf.createButton('insertorderedlist', {title : 'simple.numlist_desc', cmd : 'InsertOrderedList'})); + tb.renderTo(n); + + return { + iframeContainer : ic, + editorContainer : ed.id + '_container', + sizeContainer : sc, + deltaHeight : -20 + }; + }, + + getInfo : function() { + return { + longname : 'Simple theme', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + version : tinymce.majorVersion + "." + tinymce.minorVersion + } + } + }); + + tinymce.ThemeManager.add('simple', tinymce.themes.SimpleTheme); +})(); \ No newline at end of file diff --git a/web/js/globals/tinymce/themes/simple/img/icons.gif b/web/js/globals/tinymce/themes/simple/img/icons.gif new file mode 100644 index 0000000000000000000000000000000000000000..16af141ff0eea376a889b1e8d28e9c1cacaaab16 GIT binary patch literal 1440 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 VmjT$ODWe$zObpERYjs_s{s;8{A&me4 literal 0 HcmV?d00001 diff --git a/web/js/globals/tinymce/themes/simple/skins/o2k7/ui.css b/web/js/globals/tinymce/themes/simple/skins/o2k7/ui.css new file mode 100644 index 0000000..cf6c35d --- /dev/null +++ b/web/js/globals/tinymce/themes/simple/skins/o2k7/ui.css @@ -0,0 +1,35 @@ +/* Reset */ +.o2k7SimpleSkin table, .o2k7SimpleSkin tbody, .o2k7SimpleSkin a, .o2k7SimpleSkin img, .o2k7SimpleSkin tr, .o2k7SimpleSkin div, .o2k7SimpleSkin td, .o2k7SimpleSkin iframe, .o2k7SimpleSkin span, .o2k7SimpleSkin * {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000} + +/* Containers */ +.o2k7SimpleSkin {position:relative} +.o2k7SimpleSkin table.mceLayout {background:#E5EFFD; border:1px solid #ABC6DD;} +.o2k7SimpleSkin iframe {display:block; background:#FFF; border-bottom:1px solid #ABC6DD;} +.o2k7SimpleSkin .mceToolbar {height:26px;} + +/* Layout */ +.o2k7SimpleSkin .mceToolbar .mceToolbarStart span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px; } +.o2k7SimpleSkin .mceToolbar .mceToolbarEnd span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px} +.o2k7SimpleSkin span.mceIcon, .o2k7SimpleSkin img.mceIcon {display:block; width:20px; height:20px} +.o2k7SimpleSkin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px} + +/* Button */ +.o2k7SimpleSkin .mceButton {display:block; background:url(img/button_bg.png); width:22px; height:22px} +.o2k7SimpleSkin a.mceButton span, .o2k7SimpleSkin a.mceButton img {margin:1px 0 0 1px} +.o2k7SimpleSkin a.mceButtonEnabled:hover {background-color:#B2BBD0; background-position:0 -22px} +.o2k7SimpleSkin a.mceButtonActive {background-position:0 -44px} +.o2k7SimpleSkin .mceButtonDisabled span {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} + +/* Separator */ +.o2k7SimpleSkin .mceSeparator {display:block; background:url(img/button_bg.png) -22px 0; width:5px; height:22px} + +/* Theme */ +.o2k7SimpleSkin span.mce_bold {background-position:0 0} +.o2k7SimpleSkin span.mce_italic {background-position:-60px 0} +.o2k7SimpleSkin span.mce_underline {background-position:-140px 0} +.o2k7SimpleSkin span.mce_strikethrough {background-position:-120px 0} +.o2k7SimpleSkin span.mce_undo {background-position:-160px 0} +.o2k7SimpleSkin span.mce_redo {background-position:-100px 0} +.o2k7SimpleSkin span.mce_cleanup {background-position:-40px 0} +.o2k7SimpleSkin span.mce_insertunorderedlist {background-position:-20px 0} +.o2k7SimpleSkin span.mce_insertorderedlist {background-position:-80px 0} diff --git a/web/js/globals/tinymce/tiny_mce.js b/web/js/globals/tinymce/tiny_mce.js new file mode 100644 index 0000000..7c776ed --- /dev/null +++ b/web/js/globals/tinymce/tiny_mce.js @@ -0,0 +1 @@ +(function(d){var a=/^\s*|\s*$/g,e,c="B".replace(/A(.)|B/,"$1")==="$1";var b={majorVersion:"3",minorVersion:"3.9.2",releaseDate:"2010-09-29",_init:function(){var s=this,q=document,o=navigator,g=o.userAgent,m,f,l,k,j,r;s.isOpera=d.opera&&opera.buildNumber;s.isWebKit=/WebKit/.test(g);s.isIE=!s.isWebKit&&!s.isOpera&&(/MSIE/gi).test(g)&&(/Explorer/gi).test(o.appName);s.isIE6=s.isIE&&/MSIE [56]/.test(g);s.isGecko=!s.isWebKit&&/Gecko/.test(g);s.isMac=g.indexOf("Mac")!=-1;s.isAir=/adobeair/i.test(g);s.isIDevice=/(iPad|iPhone)/.test(g);if(d.tinyMCEPreInit){s.suffix=tinyMCEPreInit.suffix;s.baseURL=tinyMCEPreInit.base;s.query=tinyMCEPreInit.query;return}s.suffix="";f=q.getElementsByTagName("base");for(m=0;m=c.length){for(e=0,b=g.length;e=c.length||g[e]!=c[e]){f=e+1;break}}}if(g.length=g.length||g[e]!=c[e]){f=e+1;break}}}if(f==1){return h}for(e=0,b=g.length-(f-1);e=0;c--){if(f[c].length==0||f[c]=="."){continue}if(f[c]==".."){b++;continue}if(b>0){b--;continue}h.push(f[c])}c=e.length-b;if(c<=0){g=h.reverse().join("/")}else{g=e.slice(0,c).join("/")+"/"+h.reverse().join("/")}if(g.indexOf("/")!==0){g="/"+g}if(d&&g.lastIndexOf("/")!==g.length-1){g+=d}return g},getURI:function(d){var c,b=this;if(!b.source||d){c="";if(!d){if(b.protocol){c+=b.protocol+"://"}if(b.userInfo){c+=b.userInfo+"@"}if(b.host){c+=b.host}if(b.port){c+=":"+b.port}}if(b.path){c+=b.path}if(b.query){c+="?"+b.query}if(b.anchor){c+="#"+b.anchor}b.source=c}return b.source}})})();(function(){var a=tinymce.each;tinymce.create("static tinymce.util.Cookie",{getHash:function(d){var b=this.get(d),c;if(b){a(b.split("&"),function(e){e=e.split("=");c=c||{};c[unescape(e[0])]=unescape(e[1])})}return c},setHash:function(j,b,g,f,i,c){var h="";a(b,function(e,d){h+=(!h?"":"&")+escape(d)+"="+escape(e)});this.set(j,h,g,f,i,c)},get:function(i){var h=document.cookie,g,f=i+"=",d;if(!h){return}d=h.indexOf("; "+f);if(d==-1){d=h.indexOf(f);if(d!=0){return null}}else{d+=2}g=h.indexOf(";",d);if(g==-1){g=h.length}return unescape(h.substring(d+f.length,g))},set:function(i,b,g,f,h,c){document.cookie=i+"="+escape(b)+((g)?"; expires="+g.toGMTString():"")+((f)?"; path="+escape(f):"")+((h)?"; domain="+h:"")+((c)?"; secure":"")},remove:function(e,b){var c=new Date();c.setTime(c.getTime()-1000);this.set(e,"",c,b,c)}})})();tinymce.create("static tinymce.util.JSON",{serialize:function(e){var c,a,d=tinymce.util.JSON.serialize,b;if(e==null){return"null"}b=typeof e;if(b=="string"){a="\bb\tt\nn\ff\rr\"\"''\\\\";return'"'+e.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g,function(g,f){c=a.indexOf(f);if(c+1){return"\\"+a.charAt(c+1)}g=f.charCodeAt().toString(16);return"\\u"+"0000".substring(g.length)+g})+'"'}if(b=="object"){if(e.hasOwnProperty&&e instanceof Array){for(c=0,a="[";c0?",":"")+d(e[c])}return a+"]"}a="{";for(c in e){a+=typeof e[c]!="function"?(a.length>1?',"':'"')+c+'":'+d(e[c]):""}return a+"}"}return""+e},parse:function(s){try{return eval("("+s+")")}catch(ex){}}});tinymce.create("static tinymce.util.XHR",{send:function(g){var a,e,b=window,h=0;g.scope=g.scope||this;g.success_scope=g.success_scope||g.scope;g.error_scope=g.error_scope||g.scope;g.async=g.async===false?false:true;g.data=g.data||"";function d(i){a=0;try{a=new ActiveXObject(i)}catch(c){}return a}a=b.XMLHttpRequest?new XMLHttpRequest():d("Microsoft.XMLHTTP")||d("Msxml2.XMLHTTP");if(a){if(a.overrideMimeType){a.overrideMimeType(g.content_type)}a.open(g.type||(g.data?"POST":"GET"),g.url,g.async);if(g.content_type){a.setRequestHeader("Content-Type",g.content_type)}a.setRequestHeader("X-Requested-With","XMLHttpRequest");a.send(g.data);function f(){if(!g.async||a.readyState==4||h++>10000){if(g.success&&h<10000&&a.status==200){g.success.call(g.success_scope,""+a.responseText,a,g)}else{if(g.error){g.error.call(g.error_scope,h>10000?"TIMED_OUT":"GENERAL",a,g)}}a=null}else{b.setTimeout(f,10)}}if(!g.async){return f()}e=b.setTimeout(f,10)}}});(function(){var c=tinymce.extend,b=tinymce.util.JSON,a=tinymce.util.XHR;tinymce.create("tinymce.util.JSONRequest",{JSONRequest:function(d){this.settings=c({},d);this.count=0},send:function(f){var e=f.error,d=f.success;f=c(this.settings,f);f.success=function(h,g){h=b.parse(h);if(typeof(h)=="undefined"){h={error:"JSON Parse error."}}if(h.error){e.call(f.error_scope||f.scope,h.error,g)}else{d.call(f.success_scope||f.scope,h.result)}};f.error=function(h,g){e.call(f.error_scope||f.scope,h,g)};f.data=b.serialize({id:f.id||"c"+(this.count++),method:f.method,params:f.params});f.content_type="application/json";a.send(f)},"static":{sendRPC:function(d){return new tinymce.util.JSONRequest().send(d)}}})}());(function(m){var k=m.each,j=m.is,i=m.isWebKit,d=m.isIE,a=/^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,e=g("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),f=g("src,href,style,coords,shape"),c={"&":"&",'"':""","<":"<",">":">"},n=/[<>&\"]/g,b=/^([a-z0-9],?)+$/i,h=/<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,l=/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;function g(q){var p={},o;q=q.split(",");for(o=q.length;o>=0;o--){p[q[o]]=1}return p}m.create("tinymce.dom.DOMUtils",{doc:null,root:null,files:null,pixelStyles:/^(top|left|bottom|right|width|height|borderWidth)$/,props:{"for":"htmlFor","class":"className",className:"className",checked:"checked",disabled:"disabled",maxlength:"maxLength",readonly:"readOnly",selected:"selected",value:"value",id:"id",name:"name",type:"type"},DOMUtils:function(u,q){var p=this,o;p.doc=u;p.win=window;p.files={};p.cssFlicker=false;p.counter=0;p.stdMode=u.documentMode>=8;p.boxModel=!m.isIE||u.compatMode=="CSS1Compat"||p.stdMode;p.settings=q=m.extend({keep_values:false,hex_colors:1,process_html:1},q);if(m.isIE6){try{u.execCommand("BackgroundImageCache",false,true)}catch(r){p.cssFlicker=true}}if(q.valid_styles){p._styles={};k(q.valid_styles,function(t,s){p._styles[s]=m.explode(t)})}m.addUnload(p.destroy,p)},getRoot:function(){var o=this,p=o.settings;return(p&&o.get(p.root_element))||o.doc.body},getViewPort:function(p){var q,o;p=!p?this.win:p;q=p.document;o=this.boxModel?q.documentElement:q.body;return{x:p.pageXOffset||o.scrollLeft,y:p.pageYOffset||o.scrollTop,w:p.innerWidth||o.clientWidth,h:p.innerHeight||o.clientHeight}},getRect:function(s){var r,o=this,q;s=o.get(s);r=o.getPos(s);q=o.getSize(s);return{x:r.x,y:r.y,w:q.w,h:q.h}},getSize:function(r){var p=this,o,q;r=p.get(r);o=p.getStyle(r,"width");q=p.getStyle(r,"height");if(o.indexOf("px")===-1){o=0}if(q.indexOf("px")===-1){q=0}return{w:parseInt(o)||r.offsetWidth||r.clientWidth,h:parseInt(q)||r.offsetHeight||r.clientHeight}},getParent:function(q,p,o){return this.getParents(q,p,o,false)},getParents:function(z,v,s,y){var q=this,p,u=q.settings,x=[];z=q.get(z);y=y===undefined;if(u.strict_root){s=s||q.getRoot()}if(j(v,"string")){p=v;if(v==="*"){v=function(o){return o.nodeType==1}}else{v=function(o){return q.is(o,p)}}}while(z){if(z==s||!z.nodeType||z.nodeType===9){break}if(!v||v(z)){if(y){x.push(z)}else{return z}}z=z.parentNode}return y?x:null},get:function(o){var p;if(o&&this.doc&&typeof(o)=="string"){p=o;o=this.doc.getElementById(o);if(o&&o.id!==p){return this.doc.getElementsByName(p)[1]}}return o},getNext:function(p,o){return this._findSib(p,o,"nextSibling")},getPrev:function(p,o){return this._findSib(p,o,"previousSibling")},add:function(s,v,o,r,u){var q=this;return this.run(s,function(y){var x,t;x=j(v,"string")?q.doc.createElement(v):v;q.setAttribs(x,o);if(r){if(r.nodeType){x.appendChild(r)}else{q.setHTML(x,r)}}return !u?y.appendChild(x):x})},create:function(q,o,p){return this.add(this.doc.createElement(q),q,o,p,1)},createHTML:function(v,p,s){var u="",r=this,q;u+="<"+v;for(q in p){if(p.hasOwnProperty(q)){u+=" "+q+'="'+r.encode(p[q])+'"'}}if(typeof(s)!="undefined"){return u+">"+s+""}return u+" />"},remove:function(o,p){return this.run(o,function(r){var q,s;q=r.parentNode;if(!q){return null}if(p){while(s=r.firstChild){if(!m.isIE||s.nodeType!==3||s.nodeValue){q.insertBefore(s,r)}else{r.removeChild(s)}}}return q.removeChild(r)})},setStyle:function(r,o,p){var q=this;return q.run(r,function(v){var u,t;u=v.style;o=o.replace(/-(\D)/g,function(x,s){return s.toUpperCase()});if(q.pixelStyles.test(o)&&(m.is(p,"number")||/^[\-0-9\.]+$/.test(p))){p+="px"}switch(o){case"opacity":if(d){u.filter=p===""?"":"alpha(opacity="+(p*100)+")";if(!r.currentStyle||!r.currentStyle.hasLayout){u.display="inline-block"}}u[o]=u["-moz-opacity"]=u["-khtml-opacity"]=p||"";break;case"float":d?u.styleFloat=p:u.cssFloat=p;break;default:u[o]=p||""}if(q.settings.update_styles){q.setAttrib(v,"_mce_style")}})},getStyle:function(r,o,q){r=this.get(r);if(!r){return false}if(this.doc.defaultView&&q){o=o.replace(/[A-Z]/g,function(s){return"-"+s});try{return this.doc.defaultView.getComputedStyle(r,null).getPropertyValue(o)}catch(p){return null}}o=o.replace(/-(\D)/g,function(t,s){return s.toUpperCase()});if(o=="float"){o=d?"styleFloat":"cssFloat"}if(r.currentStyle&&q){return r.currentStyle[o]}return r.style[o]},setStyles:function(u,v){var q=this,r=q.settings,p;p=r.update_styles;r.update_styles=0;k(v,function(o,s){q.setStyle(u,s,o)});r.update_styles=p;if(r.update_styles){q.setAttrib(u,r.cssText)}},setAttrib:function(q,r,o){var p=this;if(!q||!r){return}if(p.settings.strict){r=r.toLowerCase()}return this.run(q,function(u){var t=p.settings;switch(r){case"style":if(!j(o,"string")){k(o,function(s,x){p.setStyle(u,x,s)});return}if(t.keep_values){if(o&&!p._isRes(o)){u.setAttribute("_mce_style",o,2)}else{u.removeAttribute("_mce_style",2)}}u.style.cssText=o;break;case"class":u.className=o||"";break;case"src":case"href":if(t.keep_values){if(t.url_converter){o=t.url_converter.call(t.url_converter_scope||p,o,r,u)}p.setAttrib(u,"_mce_"+r,o,2)}break;case"shape":u.setAttribute("_mce_style",o);break}if(j(o)&&o!==null&&o.length!==0){u.setAttribute(r,""+o,2)}else{u.removeAttribute(r,2)}})},setAttribs:function(q,r){var p=this;return this.run(q,function(o){k(r,function(s,t){p.setAttrib(o,t,s)})})},getAttrib:function(r,s,q){var o,p=this;r=p.get(r);if(!r||r.nodeType!==1){return false}if(!j(q)){q=""}if(/^(src|href|style|coords|shape)$/.test(s)){o=r.getAttribute("_mce_"+s);if(o){return o}}if(d&&p.props[s]){o=r[p.props[s]];o=o&&o.nodeValue?o.nodeValue:o}if(!o){o=r.getAttribute(s,2)}if(/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(s)){if(r[p.props[s]]===true&&o===""){return s}return o?s:""}if(r.nodeName==="FORM"&&r.getAttributeNode(s)){return r.getAttributeNode(s).nodeValue}if(s==="style"){o=o||r.style.cssText;if(o){o=p.serializeStyle(p.parseStyle(o),r.nodeName);if(p.settings.keep_values&&!p._isRes(o)){r.setAttribute("_mce_style",o)}}}if(i&&s==="class"&&o){o=o.replace(/(apple|webkit)\-[a-z\-]+/gi,"")}if(d){switch(s){case"rowspan":case"colspan":if(o===1){o=""}break;case"size":if(o==="+0"||o===20||o===0){o=""}break;case"width":case"height":case"vspace":case"checked":case"disabled":case"readonly":if(o===0){o=""}break;case"hspace":if(o===-1){o=""}break;case"maxlength":case"tabindex":if(o===32768||o===2147483647||o==="32768"){o=""}break;case"multiple":case"compact":case"noshade":case"nowrap":if(o===65535){return s}return q;case"shape":o=o.toLowerCase();break;default:if(s.indexOf("on")===0&&o){o=m._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/,"$1",""+o)}}}return(o!==undefined&&o!==null&&o!=="")?""+o:q},getPos:function(A,s){var p=this,o=0,z=0,u,v=p.doc,q;A=p.get(A);s=s||v.body;if(A){if(d&&!p.stdMode){A=A.getBoundingClientRect();u=p.boxModel?v.documentElement:v.body;o=p.getStyle(p.select("html")[0],"borderWidth");o=(o=="medium"||p.boxModel&&!p.isIE6)&&2||o;return{x:A.left+u.scrollLeft-o,y:A.top+u.scrollTop-o}}q=A;while(q&&q!=s&&q.nodeType){o+=q.offsetLeft||0;z+=q.offsetTop||0;q=q.offsetParent}q=A.parentNode;while(q&&q!=s&&q.nodeType){o-=q.scrollLeft||0;z-=q.scrollTop||0;q=q.parentNode}}return{x:o,y:z}},parseStyle:function(r){var u=this,v=u.settings,x={};if(!r){return x}function p(D,A,C){var z,B,o,y;z=x[D+"-top"+A];if(!z){return}B=x[D+"-right"+A];if(z!=B){return}o=x[D+"-bottom"+A];if(B!=o){return}y=x[D+"-left"+A];if(o!=y){return}x[C]=y;delete x[D+"-top"+A];delete x[D+"-right"+A];delete x[D+"-bottom"+A];delete x[D+"-left"+A]}function q(y,s,o,A){var z;z=x[s];if(!z){return}z=x[o];if(!z){return}z=x[A];if(!z){return}x[y]=x[s]+" "+x[o]+" "+x[A];delete x[s];delete x[o];delete x[A]}r=r.replace(/&(#?[a-z0-9]+);/g,"&$1_MCE_SEMI_");k(r.split(";"),function(s){var o,t=[];if(s){s=s.replace(/_MCE_SEMI_/g,";");s=s.replace(/url\([^\)]+\)/g,function(y){t.push(y);return"url("+t.length+")"});s=s.split(":");o=m.trim(s[1]);o=o.replace(/url\(([^\)]+)\)/g,function(z,y){return t[parseInt(y)-1]});o=o.replace(/rgb\([^\)]+\)/g,function(y){return u.toHex(y)});if(v.url_converter){o=o.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g,function(y,z){return"url("+v.url_converter.call(v.url_converter_scope||u,u.decode(z),"style",null)+")"})}x[m.trim(s[0]).toLowerCase()]=o}});p("border","","border");p("border","-width","border-width");p("border","-color","border-color");p("border","-style","border-style");p("padding","","padding");p("margin","","margin");q("border","border-width","border-style","border-color");if(d){if(x.border=="medium none"){x.border=""}}return x},serializeStyle:function(v,p){var q=this,r="";function u(s,o){if(o&&s){if(o.indexOf("-")===0){return}switch(o){case"font-weight":if(s==700){s="bold"}break;case"color":case"background-color":s=s.toLowerCase();break}r+=(r?" ":"")+o+": "+s+";"}}if(p&&q._styles){k(q._styles["*"],function(o){u(v[o],o)});k(q._styles[p.toLowerCase()],function(o){u(v[o],o)})}else{k(v,u)}return r},loadCSS:function(o){var q=this,r=q.doc,p;if(!o){o=""}p=q.select("head")[0];k(o.split(","),function(s){var t;if(q.files[s]){return}q.files[s]=true;t=q.create("link",{rel:"stylesheet",href:m._addVer(s)});if(d&&r.documentMode&&r.recalc){t.onload=function(){r.recalc();t.onload=null}}p.appendChild(t)})},addClass:function(o,p){return this.run(o,function(q){var r;if(!p){return 0}if(this.hasClass(q,p)){return q.className}r=this.removeClass(q,p);return q.className=(r!=""?(r+" "):"")+p})},removeClass:function(q,r){var o=this,p;return o.run(q,function(t){var s;if(o.hasClass(t,r)){if(!p){p=new RegExp("(^|\\s+)"+r+"(\\s+|$)","g")}s=t.className.replace(p," ");s=m.trim(s!=" "?s:"");t.className=s;if(!s){t.removeAttribute("class");t.removeAttribute("className")}return s}return t.className})},hasClass:function(p,o){p=this.get(p);if(!p||!o){return false}return(" "+p.className+" ").indexOf(" "+o+" ")!==-1},show:function(o){return this.setStyle(o,"display","block")},hide:function(o){return this.setStyle(o,"display","none")},isHidden:function(o){o=this.get(o);return !o||o.style.display=="none"||this.getStyle(o,"display")=="none"},uniqueId:function(o){return(!o?"mce_":o)+(this.counter++)},setHTML:function(q,p){var o=this;return this.run(q,function(v){var r,t,s,z,u,r;p=o.processHTML(p);if(d){function y(){while(v.firstChild){v.firstChild.removeNode()}try{v.innerHTML="
      "+p;v.removeChild(v.firstChild)}catch(x){r=o.create("div");r.innerHTML="
      "+p;k(r.childNodes,function(B,A){if(A){v.appendChild(B)}})}}if(o.settings.fix_ie_paragraphs){p=p.replace(/

      <\/p>|]+)><\/p>|/gi,' 

      ')}y();if(o.settings.fix_ie_paragraphs){s=v.getElementsByTagName("p");for(t=s.length-1,r=0;t>=0;t--){z=s[t];if(!z.hasChildNodes()){if(!z._mce_keep){r=1;break}z.removeAttribute("_mce_keep")}}}if(r){p=p.replace(/

      ]+)>|

      /ig,'

      ');p=p.replace(/<\/p>/gi,"
      ");y();if(o.settings.fix_ie_paragraphs){s=v.getElementsByTagName("DIV");for(t=s.length-1;t>=0;t--){z=s[t];if(z._mce_tmp){u=o.doc.createElement("p");z.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi,function(A,x){var B;if(x!=="_mce_tmp"){B=z.getAttribute(x);if(!B&&x==="class"){B=z.className}u.setAttribute(x,B)}});for(r=0;r]+)\/>|/gi,"",r);if(q.keep_values){if(/)/g,"\n");t=t.replace(/^[\r\n]*|[\r\n]*$/g,"");t=t.replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g,"");return t}r=r.replace(/]+|)>([\s\S]*?)<\/script>/gi,function(s,x,t){if(!x){x=' type="text/javascript"'}x=x.replace(/src=\"([^\"]+)\"?/i,function(y,z){if(q.url_converter){z=p.encode(q.url_converter.call(q.url_converter_scope||p,p.decode(z),"src","script"))}return'_mce_src="'+z+'"'});if(m.trim(t)){v.push(o(t));t=""}return""+t+""});r=r.replace(/]+|)>([\s\S]*?)<\/style>/gi,function(s,x,t){if(t){v.push(o(t));t=""}return""+t+""});r=r.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(s,x,t){return""})}r=m._replace(//g,"",r);function u(s){return s.replace(h,function(y,z,x,t){return"<"+z+x.replace(l,function(B,A,E,D,C){var F;A=A.toLowerCase();E=E||D||C||"";if(e[A]){if(E==="false"||E==="0"){return}return A+'="'+A+'"'}if(f[A]&&x.indexOf("_mce_"+A)==-1){F=p.decode(E);if(q.url_converter&&(A=="src"||A=="href")){F=q.url_converter.call(q.url_converter_scope||p,F,A,z)}if(A=="style"){F=p.serializeStyle(p.parseStyle(F),A)}return A+'="'+E+'" _mce_'+A+'="'+p.encode(F)+'"'}return B})+t+">"})}r=u(r);r=r.replace(/MCE_SCRIPT:([0-9]+)/g,function(t,s){return v[s]})}return r},getOuterHTML:function(o){var p;o=this.get(o);if(!o){return null}if(o.outerHTML!==undefined){return o.outerHTML}p=(o.ownerDocument||this.doc).createElement("body");p.appendChild(o.cloneNode(true));return p.innerHTML},setOuterHTML:function(r,p,s){var o=this;function q(u,t,x){var y,v;v=x.createElement("body");v.innerHTML=t;y=v.lastChild;while(y){o.insertAfter(y.cloneNode(true),u);y=y.previousSibling}o.remove(u)}return this.run(r,function(u){u=o.get(u);if(u.nodeType==1){s=s||u.ownerDocument||o.doc;if(d){try{if(d&&u.nodeType==1){u.outerHTML=p}else{q(u,p,s)}}catch(t){q(u,p,s)}}else{q(u,p,s)}}})},decode:function(p){var q,r,o;if(/&[\w#]+;/.test(p)){q=this.doc.createElement("div");q.innerHTML=p;r=q.firstChild;o="";if(r){do{o+=r.nodeValue}while(r=r.nextSibling)}return o||p}return p},encode:function(o){return(""+o).replace(n,function(p){return c[p]})},insertAfter:function(o,p){p=this.get(p);return this.run(o,function(r){var q,s;q=p.parentNode;s=p.nextSibling;if(s){q.insertBefore(r,s)}else{q.appendChild(r)}return r})},isBlock:function(o){if(o.nodeType&&o.nodeType!==1){return false}o=o.nodeName||o;return a.test(o)},replace:function(s,r,p){var q=this;if(j(r,"array")){s=s.cloneNode(true)}return q.run(r,function(t){if(p){k(m.grep(t.childNodes),function(o){s.appendChild(o)})}return t.parentNode.replaceChild(s,t)})},rename:function(r,o){var q=this,p;if(r.nodeName!=o.toUpperCase()){p=q.create(o);k(q.getAttribs(r),function(s){q.setAttrib(p,s.nodeName,q.getAttrib(r,s.nodeName))});q.replace(p,r,1)}return p||r},findCommonAncestor:function(q,o){var r=q,p;while(r){p=o;while(p&&r!=p){p=p.parentNode}if(r==p){break}r=r.parentNode}if(!r&&q.ownerDocument){return q.ownerDocument.documentElement}return r},toHex:function(o){var q=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(o);function p(r){r=parseInt(r).toString(16);return r.length>1?r:"0"+r}if(q){o="#"+p(q[1])+p(q[2])+p(q[3]);return o}return o},getClasses:function(){var s=this,o=[],r,u={},v=s.settings.class_filter,q;if(s.classes){return s.classes}function x(t){k(t.imports,function(y){x(y)});k(t.cssRules||t.rules,function(y){switch(y.type||1){case 1:if(y.selectorText){k(y.selectorText.split(","),function(z){z=z.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(z)||!/\.[\w\-]+$/.test(z)){return}q=z;z=m._replace(/.*\.([a-z0-9_\-]+).*/i,"$1",z);if(v&&!(z=v(z,q))){return}if(!u[z]){o.push({"class":z});u[z]=1}})}break;case 3:x(y.styleSheet);break}})}try{k(s.doc.styleSheets,x)}catch(p){}if(o.length>0){s.classes=o}return o},run:function(u,r,q){var p=this,v;if(p.doc&&typeof(u)==="string"){u=p.get(u)}if(!u){return false}q=q||this;if(!u.nodeType&&(u.length||u.length===0)){v=[];k(u,function(s,o){if(s){if(typeof(s)=="string"){s=p.doc.getElementById(s)}v.push(r.call(q,s,o))}});return v}return r.call(q,u)},getAttribs:function(q){var p;q=this.get(q);if(!q){return[]}if(d){p=[];if(q.nodeName=="OBJECT"){return q.attributes}if(q.nodeName==="OPTION"&&this.getAttrib(q,"selected")){p.push({specified:1,nodeName:"selected"})}q.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi,"").replace(/[\w:\-]+/gi,function(o){p.push({specified:1,nodeName:o})});return p}return q.attributes},destroy:function(p){var o=this;if(o.events){o.events.destroy()}o.win=o.doc=o.root=o.events=null;if(!p){m.removeUnload(o.destroy)}},createRng:function(){var o=this.doc;return o.createRange?o.createRange():new m.dom.Range(this)},nodeIndex:function(s,t){var o=0,q,r,p;if(s){for(q=s.nodeType,s=s.previousSibling,r=s;s;s=s.previousSibling){p=s.nodeType;if(t&&p==3){if(p==q||!s.nodeValue.length){continue}}o++;q=p}}return o},split:function(u,s,y){var z=this,o=z.createRng(),v,q,x;function p(A){var t,r=A.childNodes;if(A.nodeType==1&&A.getAttribute("_mce_type")=="bookmark"){return}for(t=r.length-1;t>=0;t--){p(r[t])}if(A.nodeType!=9){if(A.nodeType==3&&A.nodeValue.length>0){if(!z.isBlock(A.parentNode)||m.trim(A.nodeValue).length>0){return}}if(A.nodeType==1){r=A.childNodes;if(r.length==1&&r[0]&&r[0].nodeType==1&&r[0].getAttribute("_mce_type")=="bookmark"){A.parentNode.insertBefore(r[0],A)}if(r.length||/^(br|hr|input|img)$/i.test(A.nodeName)){return}}z.remove(A)}return A}if(u&&s){o.setStart(u.parentNode,z.nodeIndex(u));o.setEnd(s.parentNode,z.nodeIndex(s));v=o.extractContents();o=z.createRng();o.setStart(s.parentNode,z.nodeIndex(s)+1);o.setEnd(u.parentNode,z.nodeIndex(u)+1);q=o.extractContents();x=u.parentNode;x.insertBefore(p(v),u);if(y){x.replaceChild(y,s)}else{x.insertBefore(s,u)}x.insertBefore(p(q),u);z.remove(u);return y||s}},bind:function(s,o,r,q){var p=this;if(!p.events){p.events=new m.dom.EventUtils()}return p.events.add(s,o,r,q||this)},unbind:function(r,o,q){var p=this;if(!p.events){p.events=new m.dom.EventUtils()}return p.events.remove(r,o,q)},_findSib:function(r,o,p){var q=this,s=o;if(r){if(j(s,"string")){s=function(t){return q.is(t,o)}}for(r=r[p];r;r=r[p]){if(s(r)){return r}}}return null},_isRes:function(o){return/^(top|left|bottom|right|width|height)/i.test(o)||/;\s*(top|left|bottom|right|width|height)/i.test(o)}});m.DOM=new m.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(a){function b(c){var N=this,e=c.doc,S=0,E=1,j=2,D=true,R=false,U="startOffset",h="startContainer",P="endContainer",z="endOffset",k=tinymce.extend,n=c.nodeIndex;k(N,{startContainer:e,startOffset:0,endContainer:e,endOffset:0,collapsed:D,commonAncestorContainer:e,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3,setStart:q,setEnd:s,setStartBefore:g,setStartAfter:I,setEndBefore:J,setEndAfter:u,collapse:A,selectNode:x,selectNodeContents:F,compareBoundaryPoints:v,deleteContents:p,extractContents:H,cloneContents:d,insertNode:C,surroundContents:M,cloneRange:K});function q(V,t){B(D,V,t)}function s(V,t){B(R,V,t)}function g(t){q(t.parentNode,n(t))}function I(t){q(t.parentNode,n(t)+1)}function J(t){s(t.parentNode,n(t))}function u(t){s(t.parentNode,n(t)+1)}function A(t){if(t){N[P]=N[h];N[z]=N[U]}else{N[h]=N[P];N[U]=N[z]}N.collapsed=D}function x(t){g(t);u(t)}function F(t){q(t,0);s(t,t.nodeType===1?t.childNodes.length:t.nodeValue.length)}function v(W,X){var Z=N[h],Y=N[U],V=N[P],t=N[z];if(W===0){return G(Z,Y,Z,Y)}if(W===1){return G(Z,Y,V,t)}if(W===2){return G(V,t,V,t)}if(W===3){return G(V,t,Z,Y)}}function p(){m(j)}function H(){return m(S)}function d(){return m(E)}function C(Y){var V=this[h],t=this[U],X,W;if((V.nodeType===3||V.nodeType===4)&&V.nodeValue){if(!t){V.parentNode.insertBefore(Y,V)}else{if(t>=V.nodeValue.length){c.insertAfter(Y,V)}else{X=V.splitText(t);V.parentNode.insertBefore(Y,X)}}}else{if(V.childNodes.length>0){W=V.childNodes[t]}if(W){V.insertBefore(Y,W)}else{V.appendChild(Y)}}}function M(V){var t=N.extractContents();N.insertNode(V);V.appendChild(t);N.selectNode(V)}function K(){return k(new b(c),{startContainer:N[h],startOffset:N[U],endContainer:N[P],endOffset:N[z],collapsed:N.collapsed,commonAncestorContainer:N.commonAncestorContainer})}function O(t,V){var W;if(t.nodeType==3){return t}if(V<0){return t}W=t.firstChild;while(W&&V>0){--V;W=W.nextSibling}if(W){return W}return t}function l(){return(N[h]==N[P]&&N[U]==N[z])}function G(X,Z,V,Y){var aa,W,t,ab,ad,ac;if(X==V){if(Z==Y){return 0}if(Z0){N.collapse(V)}}else{N.collapse(V)}N.collapsed=l();N.commonAncestorContainer=c.findCommonAncestor(N[h],N[P])}function m(ab){var aa,X=0,ad=0,V,Z,W,Y,t,ac;if(N[h]==N[P]){return f(ab)}for(aa=N[P],V=aa.parentNode;V;aa=V,V=V.parentNode){if(V==N[h]){return r(aa,ab)}++X}for(aa=N[h],V=aa.parentNode;V;aa=V,V=V.parentNode){if(V==N[P]){return T(aa,ab)}++ad}Z=ad-X;W=N[h];while(Z>0){W=W.parentNode;Z--}Y=N[P];while(Z<0){Y=Y.parentNode;Z++}for(t=W.parentNode,ac=Y.parentNode;t!=ac;t=t.parentNode,ac=ac.parentNode){W=t;Y=ac}return o(W,Y,ab)}function f(Z){var ab,Y,X,aa,t,W,V;if(Z!=j){ab=e.createDocumentFragment()}if(N[U]==N[z]){return ab}if(N[h].nodeType==3){Y=N[h].nodeValue;X=Y.substring(N[U],N[z]);if(Z!=E){N[h].deleteData(N[U],N[z]-N[U]);N.collapse(D)}if(Z==j){return}ab.appendChild(e.createTextNode(X));return ab}aa=O(N[h],N[U]);t=N[z]-N[U];while(t>0){W=aa.nextSibling;V=y(aa,Z);if(ab){ab.appendChild(V)}--t;aa=W}if(Z!=E){N.collapse(D)}return ab}function r(ab,Y){var aa,Z,V,t,X,W;if(Y!=j){aa=e.createDocumentFragment()}Z=i(ab,Y);if(aa){aa.appendChild(Z)}V=n(ab);t=V-N[U];if(t<=0){if(Y!=E){N.setEndBefore(ab);N.collapse(R)}return aa}Z=ab.previousSibling;while(t>0){X=Z.previousSibling;W=y(Z,Y);if(aa){aa.insertBefore(W,aa.firstChild)}--t;Z=X}if(Y!=E){N.setEndBefore(ab);N.collapse(R)}return aa}function T(Z,Y){var ab,V,aa,t,X,W;if(Y!=j){ab=e.createDocumentFragment()}aa=Q(Z,Y);if(ab){ab.appendChild(aa)}V=n(Z);++V;t=N[z]-V;aa=Z.nextSibling;while(t>0){X=aa.nextSibling;W=y(aa,Y);if(ab){ab.appendChild(W)}--t;aa=X}if(Y!=E){N.setStartAfter(Z);N.collapse(D)}return ab}function o(Z,t,ac){var W,ae,Y,aa,ab,V,ad,X;if(ac!=j){ae=e.createDocumentFragment()}W=Q(Z,ac);if(ae){ae.appendChild(W)}Y=Z.parentNode;aa=n(Z);ab=n(t);++aa;V=ab-aa;ad=Z.nextSibling;while(V>0){X=ad.nextSibling;W=y(ad,ac);if(ae){ae.appendChild(W)}ad=X;--V}W=i(t,ac);if(ae){ae.appendChild(W)}if(ac!=E){N.setStartAfter(Z);N.collapse(D)}return ae}function i(aa,ab){var W=O(N[P],N[z]-1),ac,Z,Y,t,V,X=W!=N[P];if(W==aa){return L(W,X,R,ab)}ac=W.parentNode;Z=L(ac,R,R,ab);while(ac){while(W){Y=W.previousSibling;t=L(W,X,R,ab);if(ab!=j){Z.insertBefore(t,Z.firstChild)}X=D;W=Y}if(ac==aa){return Z}W=ac.previousSibling;ac=ac.parentNode;V=L(ac,R,R,ab);if(ab!=j){V.appendChild(Z)}Z=V}}function Q(aa,ab){var X=O(N[h],N[U]),Y=X!=N[h],ac,Z,W,t,V;if(X==aa){return L(X,Y,D,ab)}ac=X.parentNode;Z=L(ac,R,D,ab);while(ac){while(X){W=X.nextSibling;t=L(X,Y,D,ab);if(ab!=j){Z.appendChild(t)}Y=D;X=W}if(ac==aa){return Z}X=ac.nextSibling;ac=ac.parentNode;V=L(ac,R,D,ab);if(ab!=j){V.appendChild(Z)}Z=V}}function L(t,Y,ab,ac){var X,W,Z,V,aa;if(Y){return y(t,ac)}if(t.nodeType==3){X=t.nodeValue;if(ab){V=N[U];W=X.substring(V);Z=X.substring(0,V)}else{V=N[z];W=X.substring(0,V);Z=X.substring(V)}if(ac!=E){t.nodeValue=Z}if(ac==j){return}aa=t.cloneNode(R);aa.nodeValue=W;return aa}if(ac==j){return}return t.cloneNode(R)}function y(V,t){if(t!=j){return t==E?V.cloneNode(D):V}V.parentNode.removeChild(V)}}a.Range=b})(tinymce.dom);(function(){function a(g){var i=this,j="\uFEFF",e,h,d=g.dom,c=true,f=false;function b(){var n=g.getRng(),k=d.createRng(),m,o;m=n.item?n.item(0):n.parentElement();if(m.ownerDocument!=d.doc){return k}if(n.item||!m.hasChildNodes()){k.setStart(m.parentNode,d.nodeIndex(m));k.setEnd(k.startContainer,k.startOffset+1);return k}o=g.isCollapsed();function l(s){var u,q,t,p,A=0,x,y,z,r,v;r=n.duplicate();r.collapse(s);u=d.create("a");z=r.parentElement();if(!z.hasChildNodes()){k[s?"setStart":"setEnd"](z,0);return}z.appendChild(u);r.moveToElementText(u);v=n.compareEndPoints(s?"StartToStart":"EndToEnd",r);if(v>0){k[s?"setStartAfter":"setEndAfter"](z);d.remove(u);return}p=tinymce.grep(z.childNodes);x=p.length-1;while(A<=x){y=Math.floor((A+x)/2);z.insertBefore(u,p[y]);r.moveToElementText(u);v=n.compareEndPoints(s?"StartToStart":"EndToEnd",r);if(v>0){A=y+1}else{if(v<0){x=y-1}else{found=true;break}}}q=v>0||y==0?u.nextSibling:u.previousSibling;if(q.nodeType==1){d.remove(u);t=d.nodeIndex(q);q=q.parentNode;if(!s||y>0){t++}}else{if(v>0||y==0){r.setEndPoint(s?"StartToStart":"EndToEnd",n);t=r.text.length}else{r.setEndPoint(s?"StartToStart":"EndToEnd",n);t=q.nodeValue.length-r.text.length}d.remove(u)}k[s?"setStart":"setEnd"](q,t)}l(true);if(!o){l()}return k}this.addRange=function(k){var p,n,m,r,u,s,t=g.dom.doc,o=t.body;function l(B){var x,A,v,z,y;v=d.create("a");x=B?m:u;A=B?r:s;z=p.duplicate();if(x==t){x=o;A=0}if(x.nodeType==3){x.parentNode.insertBefore(v,x);z.moveToElementText(v);z.moveStart("character",A);d.remove(v);p.setEndPoint(B?"StartToStart":"EndToEnd",z)}else{y=x.childNodes;if(y.length){if(A>=y.length){d.insertAfter(v,y[y.length-1])}else{x.insertBefore(v,y[A])}z.moveToElementText(v)}else{v=t.createTextNode(j);x.appendChild(v);z.moveToElementText(v.parentNode);z.collapse(c)}p.setEndPoint(B?"StartToStart":"EndToEnd",z);d.remove(v)}}this.destroy();m=k.startContainer;r=k.startOffset;u=k.endContainer;s=k.endOffset;p=o.createTextRange();if(m==u&&m.nodeType==1&&r==s-1){if(r==s-1){try{n=o.createControlRange();n.addElement(m.childNodes[r]);n.select();n.scrollIntoView();return}catch(q){}}}l(true);l();p.select();p.scrollIntoView()};this.getRangeAt=function(){if(!e||!tinymce.dom.RangeUtils.compareRanges(h,g.getRng())){e=b();h=g.getRng()}try{e.startContainer.nextSibling}catch(k){e=b();h=null}return e};this.destroy=function(){h=e=null}}tinymce.dom.TridentSelection=a})();(function(d){var f=d.each,c=d.DOM,b=d.isIE,e=d.isWebKit,a;d.create("tinymce.dom.EventUtils",{EventUtils:function(){this.inits=[];this.events=[]},add:function(m,p,l,j){var g,h=this,i=h.events,k;if(p instanceof Array){k=[];f(p,function(o){k.push(h.add(m,o,l,j))});return k}if(m&&m.hasOwnProperty&&m instanceof Array){k=[];f(m,function(n){n=c.get(n);k.push(h.add(n,p,l,j))});return k}m=c.get(m);if(!m){return}g=function(n){if(h.disabled){return}n=n||window.event;if(n&&b){if(!n.target){n.target=n.srcElement}d.extend(n,h._stoppers)}if(!j){return l(n)}return l.call(j,n)};if(p=="unload"){d.unloads.unshift({func:g});return g}if(p=="init"){if(h.domLoaded){g()}else{h.inits.push(g)}return g}i.push({obj:m,name:p,func:l,cfunc:g,scope:j});h._add(m,p,g);return l},remove:function(l,m,k){var h=this,g=h.events,i=false,j;if(l&&l.hasOwnProperty&&l instanceof Array){j=[];f(l,function(n){n=c.get(n);j.push(h.remove(n,m,k))});return j}l=c.get(l);f(g,function(o,n){if(o.obj==l&&o.name==m&&(!k||(o.func==k||o.cfunc==k))){g.splice(n,1);h._remove(l,m,o.cfunc);i=true;return false}});return i},clear:function(l){var j=this,g=j.events,h,k;if(l){l=c.get(l);for(h=g.length-1;h>=0;h--){k=g[h];if(k.obj===l){j._remove(k.obj,k.name,k.cfunc);k.obj=k.cfunc=null;g.splice(h,1)}}}},cancel:function(g){if(!g){return false}this.stop(g);return this.prevent(g)},stop:function(g){if(g.stopPropagation){g.stopPropagation()}else{g.cancelBubble=true}return false},prevent:function(g){if(g.preventDefault){g.preventDefault()}else{g.returnValue=false}return false},destroy:function(){var g=this;f(g.events,function(j,h){g._remove(j.obj,j.name,j.cfunc);j.obj=j.cfunc=null});g.events=[];g=null},_add:function(h,i,g){if(h.attachEvent){h.attachEvent("on"+i,g)}else{if(h.addEventListener){h.addEventListener(i,g,false)}else{h["on"+i]=g}}},_remove:function(i,j,h){if(i){try{if(i.detachEvent){i.detachEvent("on"+j,h)}else{if(i.removeEventListener){i.removeEventListener(j,h,false)}else{i["on"+j]=null}}}catch(g){}}},_pageInit:function(h){var g=this;if(g.domLoaded){return}g.domLoaded=true;f(g.inits,function(i){i()});g.inits=[]},_wait:function(i){var g=this,h=i.document;if(i.tinyMCE_GZ&&tinyMCE_GZ.loaded){g.domLoaded=1;return}if(h.attachEvent){h.attachEvent("onreadystatechange",function(){if(h.readyState==="complete"){h.detachEvent("onreadystatechange",arguments.callee);g._pageInit(i)}});if(h.documentElement.doScroll&&i==i.top){(function(){if(g.domLoaded){return}try{h.documentElement.doScroll("left")}catch(j){setTimeout(arguments.callee,0);return}g._pageInit(i)})()}}else{if(h.addEventListener){g._add(i,"DOMContentLoaded",function(){g._pageInit(i)})}}g._add(i,"load",function(){g._pageInit(i)})},_stoppers:{preventDefault:function(){this.returnValue=false},stopPropagation:function(){this.cancelBubble=true}}});a=d.dom.Event=new d.dom.EventUtils();a._wait(window);d.addUnload(function(){a.destroy()})})(tinymce);(function(a){a.dom.Element=function(f,d){var b=this,e,c;b.settings=d=d||{};b.id=f;b.dom=e=d.dom||a.DOM;if(!a.isIE){c=e.get(b.id)}a.each(("getPos,getRect,getParent,add,setStyle,getStyle,setStyles,setAttrib,setAttribs,getAttrib,addClass,removeClass,hasClass,getOuterHTML,setOuterHTML,remove,show,hide,isHidden,setHTML,get").split(/,/),function(g){b[g]=function(){var h=[f],j;for(j=0;j_';if(k.startContainer==l&&k.endContainer==l){l.body.innerHTML=j}else{k.deleteContents();if(l.body.childNodes.length==0){l.body.innerHTML=j}else{if(k.createContextualFragment){k.insertNode(k.createContextualFragment(j))}else{var m=l.createDocumentFragment(),f=l.createElement("div");m.appendChild(f);f.outerHTML=j;k.insertNode(m)}}}n=g.dom.get("__caret");k=l.createRange();k.setStartBefore(n);k.setEndBefore(n);g.setRng(k);g.dom.remove("__caret")}else{if(k.item){l.execCommand("Delete",false,null);k=g.getRng()}k.pasteHTML(j)}g.onSetContent.dispatch(g,i)},getStart:function(){var g=this.getRng(),h,f,j,i;if(g.duplicate||g.item){if(g.item){return g.item(0)}j=g.duplicate();j.collapse(1);h=j.parentElement();f=i=g.parentElement();while(i=i.parentNode){if(i==h){h=f;break}}if(h&&h.nodeName=="BODY"){return h.firstChild||h}return h}else{h=g.startContainer;if(h.nodeType==1&&h.hasChildNodes()){h=h.childNodes[Math.min(h.childNodes.length-1,g.startOffset)]}if(h&&h.nodeType==3){return h.parentNode}return h}},getEnd:function(){var g=this,h=g.getRng(),i,f;if(h.duplicate||h.item){if(h.item){return h.item(0)}h=h.duplicate();h.collapse(0);i=h.parentElement();if(i&&i.nodeName=="BODY"){return i.lastChild||i}return i}else{i=h.endContainer;f=h.endOffset;if(i.nodeType==1&&i.hasChildNodes()){i=i.childNodes[f>0?f-1:f]}if(i&&i.nodeType==3){return i.parentNode}return i}},getBookmark:function(q,r){var u=this,m=u.dom,g,j,i,n,h,o,p,l="\uFEFF",s;function f(v,x){var t=0;d(m.select(v),function(z,y){if(z==x){t=y}});return t}if(q==2){function k(){var v=u.getRng(true),t=m.getRoot(),x={};function y(B,G){var A=B[G?"startContainer":"endContainer"],F=B[G?"startOffset":"endOffset"],z=[],C,E,D=0;if(A.nodeType==3){if(r){for(C=A.previousSibling;C&&C.nodeType==3;C=C.previousSibling){F+=C.nodeValue.length}}z.push(F)}else{E=A.childNodes;if(F>=E.length&&E.length){D=1;F=Math.max(0,E.length-1)}z.push(u.dom.nodeIndex(E[F],r)+D)}for(;A&&A!=t;A=A.parentNode){z.push(u.dom.nodeIndex(A,r))}return z}x.start=y(v,true);if(!u.isCollapsed()){x.end=y(v)}return x}return k()}if(q){return{rng:u.getRng()}}g=u.getRng();i=m.uniqueId();n=tinyMCE.activeEditor.selection.isCollapsed();s="overflow:hidden;line-height:0px";if(g.duplicate||g.item){if(!g.item){j=g.duplicate();g.collapse();g.pasteHTML(''+l+"");if(!n){j.collapse(false);j.pasteHTML(''+l+"")}}else{o=g.item(0);h=o.nodeName;return{name:h,index:f(h,o)}}}else{o=u.getNode();h=o.nodeName;if(h=="IMG"){return{name:h,index:f(h,o)}}j=g.cloneRange();if(!n){j.collapse(false);j.insertNode(m.create("span",{_mce_type:"bookmark",id:i+"_end",style:s},l))}g.collapse(true);g.insertNode(m.create("span",{_mce_type:"bookmark",id:i+"_start",style:s},l))}u.moveToBookmark({id:i,keep:1});return{id:i}},moveToBookmark:function(n){var r=this,l=r.dom,i,h,f,q,j,s,o,p;if(r.tridentSel){r.tridentSel.destroy()}if(n){if(n.start){f=l.createRng();q=l.getRoot();function g(z){var t=n[z?"start":"end"],v,x,y,u;if(t){for(x=q,v=t.length-1;v>=1;v--){u=x.childNodes;if(u.length){x=u[t[v]]}}if(z){f.setStart(x,t[0])}else{f.setEnd(x,t[0])}}}g(true);g();r.setRng(f)}else{if(n.id){function k(A){var u=l.get(n.id+"_"+A),z,t,x,y,v=n.keep;if(u){z=u.parentNode;if(A=="start"){if(!v){t=l.nodeIndex(u)}else{z=u.firstChild;t=1}j=s=z;o=p=t}else{if(!v){t=l.nodeIndex(u)}else{z=u.firstChild;t=1}s=z;p=t}if(!v){y=u.previousSibling;x=u.nextSibling;d(c.grep(u.childNodes),function(B){if(B.nodeType==3){B.nodeValue=B.nodeValue.replace(/\uFEFF/g,"")}});while(u=l.get(n.id+"_"+A)){l.remove(u,1)}if(y&&x&&y.nodeType==x.nodeType&&y.nodeType==3&&!c.isOpera){t=y.nodeValue.length;y.appendData(x.nodeValue);l.remove(x);if(A=="start"){j=s=y;o=p=t}else{s=y;p=t}}}}}function m(t){if(!a&&l.isBlock(t)&&!t.innerHTML){t.innerHTML='
      '}return t}k("start");k("end");if(j){f=l.createRng();f.setStart(m(j),o);f.setEnd(m(s),p);r.setRng(f)}}else{if(n.name){r.select(l.select(n.name)[n.index])}else{if(n.rng){r.setRng(n.rng)}}}}}},select:function(k,j){var i=this,l=i.dom,g=l.createRng(),f;f=l.nodeIndex(k);g.setStart(k.parentNode,f);g.setEnd(k.parentNode,f+1);if(j){function h(m,o){var n=new c.dom.TreeWalker(m,m);do{if(m.nodeType==3&&c.trim(m.nodeValue).length!=0){if(o){g.setStart(m,0)}else{g.setEnd(m,m.nodeValue.length)}return}if(m.nodeName=="BR"){if(o){g.setStartBefore(m)}else{g.setEndBefore(m)}return}}while(m=(o?n.next():n.prev()))}h(k,1);h(k)}i.setRng(g);return k},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}if(h.compareEndPoints){return h.compareEndPoints("StartToEnd",h)===0}return !g||h.collapsed},collapse:function(f){var g=this,h=g.getRng(),i;if(h.item){i=h.item(0);h=this.win.document.body.createTextRange();h.moveToElementText(i)}h.collapse(!!f);g.setRng(h)},getSel:function(){var g=this,f=this.win;return f.getSelection?f.getSelection():f.document.selection},getRng:function(l){var g=this,h,i,k,j=g.win.document;if(l&&g.tridentSel){return g.tridentSel.getRangeAt(0)}try{if(h=g.getSel()){i=h.rangeCount>0?h.getRangeAt(0):(h.createRange?h.createRange():j.createRange())}}catch(f){}if(c.isIE&&i.setStart&&j.selection.createRange().item){k=j.selection.createRange().item(0);i=j.createRange();i.setStartBefore(k);i.setEndAfter(k)}if(!i){i=j.createRange?j.createRange():j.body.createTextRange()}if(g.selectedRange&&g.explicitRange){if(i.compareBoundaryPoints(i.START_TO_START,g.selectedRange)===0&&i.compareBoundaryPoints(i.END_TO_END,g.selectedRange)===0){i=g.explicitRange}else{g.selectedRange=null;g.explicitRange=null}}return i},setRng:function(i){var h,g=this;if(!g.tridentSel){h=g.getSel();if(h){g.explicitRange=i;h.removeAllRanges();h.addRange(i);g.selectedRange=h.getRangeAt(0)}}else{if(i.cloneRange){g.tridentSel.addRange(i);return}try{i.select()}catch(f){}}},setNode:function(g){var f=this;f.setContent(f.dom.getOuterHTML(g));return g},getNode:function(){var g=this,f=g.getRng(),h=g.getSel(),i;if(f.setStart){if(!f){return g.dom.getRoot()}i=f.commonAncestorContainer;if(!f.collapsed){if(f.startContainer==f.endContainer){if(f.startOffset-f.endOffset<2){if(f.startContainer.hasChildNodes()){i=f.startContainer.childNodes[f.startOffset]}}}if(c.isWebKit&&h.anchorNode&&h.anchorNode.nodeType==1){return h.anchorNode.childNodes[h.anchorOffset]}}if(i&&i.nodeType==3){return i.parentNode}return i}return f.item?f.item(0):f.parentElement()},getSelectedBlocks:function(g,f){var i=this,j=i.dom,m,h,l,k=[];m=j.getParent(g||i.getStart(),j.isBlock);h=j.getParent(f||i.getEnd(),j.isBlock);if(m){k.push(m)}if(m&&h&&m!=h){l=m;while((l=l.nextSibling)&&l!=h){if(j.isBlock(l)){k.push(l)}}}if(h&&m!=h){k.push(h)}return k},destroy:function(g){var f=this;f.win=null;if(f.tridentSel){f.tridentSel.destroy()}if(!g){c.removeUnload(f.destroy)}},_fixIESelection:function(){var m=this.dom,l=m.doc,g=l.body,i,j;l.documentElement.unselectable=true;function k(n,q){var o=g.createTextRange();try{o.moveToPoint(n,q)}catch(p){o=null}return o}function h(o){var n;if(o.button){n=k(o.x,o.y);if(n){if(n.compareEndPoints("StartToStart",j)>0){n.setEndPoint("StartToStart",j)}else{n.setEndPoint("EndToEnd",j)}n.select()}}else{f()}}function f(){m.unbind(l,"mouseup",f);m.unbind(l,"mousemove",h);i=0}m.bind(l,"mousedown",function(n){if(n.target.nodeName==="HTML"){if(i){f()}i=1;j=k(n.x,n.y);if(j){m.bind(l,"mouseup",f);m.bind(l,"mousemove",h);m.win.focus();j.select()}}})}})})(tinymce);(function(a){a.create("tinymce.dom.XMLWriter",{node:null,XMLWriter:function(c){function b(){var e=document.implementation;if(!e||!e.createDocument){try{return new ActiveXObject("MSXML2.DOMDocument")}catch(d){}try{return new ActiveXObject("Microsoft.XmlDom")}catch(d){}}else{return e.createDocument("","",null)}}this.doc=b();this.valid=a.isOpera||a.isWebKit;this.reset()},reset:function(){var b=this,c=b.doc;if(c.firstChild){c.removeChild(c.firstChild)}b.node=c.appendChild(c.createElement("html"))},writeStartElement:function(c){var b=this;b.node=b.node.appendChild(b.doc.createElement(c))},writeAttribute:function(c,b){if(this.valid){b=b.replace(/>/g,"%MCGT%")}this.node.setAttribute(c,b)},writeEndElement:function(){this.node=this.node.parentNode},writeFullEndElement:function(){var b=this,c=b.node;c.appendChild(b.doc.createTextNode(""));b.node=c.parentNode},writeText:function(b){if(this.valid){b=b.replace(/>/g,"%MCGT%")}this.node.appendChild(this.doc.createTextNode(b))},writeCDATA:function(b){this.node.appendChild(this.doc.createCDATASection(b))},writeComment:function(b){if(a.isIE){b=b.replace(/^\-|\-$/g," ")}this.node.appendChild(this.doc.createComment(b.replace(/\-\-/g," ")))},getContent:function(){var b;b=this.doc.xml||new XMLSerializer().serializeToString(this.doc);b=b.replace(/<\?[^?]+\?>|]*>|<\/html>||]+>/g,"");b=b.replace(/ ?\/>/g," />");if(this.valid){b=b.replace(/\%MCGT%/g,">")}return b}})})(tinymce);(function(c){var d=/[&\"<>]/g,b=/[<>&]/g,a={"&":"&",'"':""","<":"<",">":">"};c.create("tinymce.dom.StringWriter",{str:null,tags:null,count:0,settings:null,indent:null,StringWriter:function(e){this.settings=c.extend({indent_char:" ",indentation:0},e);this.reset()},reset:function(){this.indent="";this.str="";this.tags=[];this.count=0},writeStartElement:function(e){this._writeAttributesEnd();this.writeRaw("<"+e);this.tags.push(e);this.inAttr=true;this.count++;this.elementCount=this.count;this.attrs={}},writeAttribute:function(g,e){var f=this;if(!f.attrs[g]){f.writeRaw(" "+f.encode(g,true)+'="'+f.encode(e,true)+'"');f.attrs[g]=e}},writeEndElement:function(){var e;if(this.tags.length>0){e=this.tags.pop();if(this._writeAttributesEnd(1)){this.writeRaw("")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw("");if(this.settings.indentation>0){this.writeRaw("\n")}}},writeText:function(e){this._writeAttributesEnd();this.writeRaw(this.encode(e));this.count++},writeCDATA:function(e){this._writeAttributesEnd();this.writeRaw("");this.count++},writeComment:function(e){this._writeAttributesEnd();this.writeRaw("");this.count++},writeRaw:function(e){this.str+=e},encode:function(f,e){return f.replace(e?d:b,function(g){return a[g]})},getContent:function(){return this.str},_writeAttributesEnd:function(e){if(!this.inAttr){return}this.inAttr=false;if(e&&this.elementCount==this.count){this.writeRaw(" />");return false}this.writeRaw(">");return true}})})(tinymce);(function(e){var g=e.extend,f=e.each,b=e.util.Dispatcher,d=e.isIE,a=e.isGecko;function c(h){return h.replace(/([?+*])/g,".$1")}e.create("tinymce.dom.Serializer",{Serializer:function(j){var i=this;i.key=0;i.onPreProcess=new b(i);i.onPostProcess=new b(i);try{i.writer=new e.dom.XMLWriter()}catch(h){i.writer=new e.dom.StringWriter()}if(e.isIE&&document.documentMode>8){i.writer=new e.dom.StringWriter()}i.settings=j=g({dom:e.DOM,valid_nodes:0,node_filter:0,attr_filter:0,invalid_attrs:/^(_mce_|_moz_|sizset|sizcache)/,closed:/^(br|hr|input|meta|img|link|param|area)$/,entity_encoding:"named",entities:"160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro",valid_elements:"*[*]",extended_valid_elements:0,invalid_elements:0,fix_table_elements:1,fix_list_elements:true,fix_content_duplication:true,convert_fonts_to_spans:false,font_size_classes:0,apply_source_formatting:0,indent_mode:"simple",indent_char:"\t",indent_levels:1,remove_linebreaks:1,remove_redundant_brs:1,element_format:"xhtml"},j);i.dom=j.dom;i.schema=j.schema;if(j.entity_encoding=="named"&&!j.entities){j.entity_encoding="raw"}if(j.remove_redundant_brs){i.onPostProcess.add(function(k,l){l.content=l.content.replace(/(
      \s*)+<\/(p|h[1-6]|div|li)>/gi,function(n,m,o){if(/^
      \s*<\//.test(n)){return""}return n})})}if(j.element_format=="html"){i.onPostProcess.add(function(k,l){l.content=l.content.replace(/<([^>]+) \/>/g,"<$1>")})}if(j.fix_list_elements){i.onPreProcess.add(function(v,s){var l,z,y=["ol","ul"],u,t,q,k=/^(OL|UL)$/,A;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p=1767){f(i.dom.select("p table",l.node).reverse(),function(p){var o=i.dom.getParent(p.parentNode,"table,p");if(o.nodeName!="TABLE"){try{i.dom.split(o,p)}catch(m){}}})}})}},setEntities:function(o){var n=this,j,m,h={},k;if(n.entityLookup){return}j=o.split(",");for(m=0;m1){f(q[1].split("|"),function(u){var p={},t;k=k||[];u=u.replace(/::/g,"~");u=/^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(u);u[2]=u[2].replace(/~/g,":");if(u[1]=="!"){r=r||[];r.push(u[2])}if(u[1]=="-"){for(t=0;t=1767)){p=j.createHTMLDocument("");f(r.nodeName=="BODY"?r.childNodes:[r],function(h){p.body.appendChild(p.importNode(h,true))});if(r.nodeName!="BODY"){r=p.body.firstChild}else{r=p.body}i=k.dom.doc;k.dom.doc=p}k.key=""+(parseInt(k.key)+1);if(!q.no_events){q.node=r;k.onPreProcess.dispatch(k,q)}k.writer.reset();k._info=q;k._serializeNode(r,q.getInner);q.content=k.writer.getContent();if(i){k.dom.doc=i}if(!q.no_events){k.onPostProcess.dispatch(k,q)}k._postProcess(q);q.node=null;return e.trim(q.content)},_postProcess:function(n){var i=this,k=i.settings,j=n.content,m=[],l;if(n.format=="html"){l=i._protect({content:j,patterns:[{pattern:/(]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/(]*>)(.*?)(<\/style>)/g},{pattern:/(]*>)(.*?)(<\/pre>)/g,encode:1},{pattern:/()/g}]});j=l.content;if(k.entity_encoding!=="raw"){j=i._encode(j)}if(!n.set){j=e._replace(/

      \s+<\/p>|]+)>\s+<\/p>/g,k.entity_encoding=="numeric"?" 

      ":" 

      ",j);if(k.remove_linebreaks){j=j.replace(/\r?\n|\r/g," ");j=e._replace(/(<[^>]+>)\s+/g,"$1 ",j);j=e._replace(/\s+(<\/[^>]+>)/g," $1",j);j=e._replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g,"<$1 $2>",j);j=e._replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g,"<$1>",j);j=e._replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g,"",j)}if(k.apply_source_formatting&&k.indent_mode=="simple"){j=e._replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g,"\n<$1$2$3>\n",j);j=e._replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g,"\n<$1$2>",j);j=e._replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g,"\n",j);j=j.replace(/\n\n/g,"\n")}}j=i._unprotect(j,l);j=e._replace(//g,"",j);if(k.entity_encoding=="raw"){j=e._replace(/

       <\/p>|]+)> <\/p>/g,"\u00a0

      ",j)}j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,p,o){return""+i.dom.decode(o.replace(//g,""))+""})}n.content=j},_serializeNode:function(E,J){var A=this,B=A.settings,y=A.writer,q,j,u,G,F,I,C,h,z,k,r,D,p,m,H,o,x;if(!B.node_filter||B.node_filter(E)){switch(E.nodeType){case 1:if(E.hasAttribute?E.hasAttribute("_mce_bogus"):E.getAttribute("_mce_bogus")){return}p=H=false;q=E.hasChildNodes();k=E.getAttribute("_mce_name")||E.nodeName.toLowerCase();o=E.getAttribute("_mce_type");if(o){if(!A._info.cleanup){p=true;return}else{H=1}}if(d){x=E.scopeName;if(x&&x!=="HTML"&&x!=="html"){k=x+":"+k}}if(k.indexOf("mce:")===0){k=k.substring(4)}if(!H){if(!A.validElementsRE||!A.validElementsRE.test(k)||(A.invalidElementsRE&&A.invalidElementsRE.test(k))||J){p=true;break}}if(d){if(B.fix_content_duplication){if(E._mce_serialized==A.key){return}E._mce_serialized=A.key}if(k.charAt(0)=="/"){k=k.substring(1)}}else{if(a){if(E.nodeName==="BR"&&E.getAttribute("type")=="_moz"){return}}}if(B.validate_children){if(A.elementName&&!A.schema.isValid(A.elementName,k)){p=true;break}A.elementName=k}r=A.findRule(k);if(!r){p=true;break}k=r.name||k;m=B.closed.test(k);if((!q&&r.noEmpty)||(d&&!k)){p=true;break}if(r.requiredAttribs){I=r.requiredAttribs;for(G=I.length-1;G>=0;G--){if(this.dom.getAttrib(E,I[G])!==""){break}}if(G==-1){p=true;break}}y.writeStartElement(k);if(r.attribs){for(G=0,C=r.attribs,F=C.length;G-1;G--){h=C[G];if(h.specified){I=h.nodeName.toLowerCase();if(B.invalid_attrs.test(I)||!r.validAttribsRE.test(I)){continue}D=A.findAttribRule(r,I);z=A._getAttrib(E,D,I);if(z!==null){y.writeAttribute(I,z)}}}}if(o&&H){y.writeAttribute("_mce_type",o)}if(k==="script"&&e.trim(E.innerHTML)){y.writeText("// ");y.writeCDATA(E.innerHTML.replace(/|<\[CDATA\[|\]\]>/g,""));q=false;break}if(r.padd){if(q&&(u=E.firstChild)&&u.nodeType===1&&E.childNodes.length===1){if(u.hasAttribute?u.hasAttribute("_mce_bogus"):u.getAttribute("_mce_bogus")){y.writeText("\u00a0")}}else{if(!q){y.writeText("\u00a0")}}}break;case 3:if(B.validate_children&&A.elementName&&!A.schema.isValid(A.elementName,"#text")){return}return y.writeText(E.nodeValue);case 4:return y.writeCDATA(E.nodeValue);case 8:return y.writeComment(E.nodeValue)}}else{if(E.nodeType==1){q=E.hasChildNodes()}}if(q&&!m){u=E.firstChild;while(u){A._serializeNode(u);A.elementName=k;u=u.nextSibling}}if(!p){if(!m){y.writeFullEndElement()}else{y.writeEndElement()}}},_protect:function(j){var i=this;j.items=j.items||[];function h(l){return l.replace(/[\r\n\\]/g,function(m){if(m==="\n"){return"\\n"}else{if(m==="\\"){return"\\\\"}}return"\\r"})}function k(l){return l.replace(/\\[\\rn]/g,function(m){if(m==="\\n"){return"\n"}else{if(m==="\\\\"){return"\\"}}return"\r"})}f(j.patterns,function(l){j.content=k(h(j.content).replace(l.pattern,function(n,o,m,p){m=k(m);if(l.encode){m=i._encode(m)}j.items.push(m);return o+""+p}))});return j},_unprotect:function(i,j){i=i.replace(/\"))}if(a&&j.ListBox){if(a.Button||a.SplitButton){e+=b.createHTML("td",{"class":"mceToolbarEnd"},b.createHTML("span",null,""))}}if(b.stdMode){e+=''+j.renderHTML()+""}else{e+=""+j.renderHTML()+""}if(f&&j.ListBox){if(f.Button||f.SplitButton){e+=b.createHTML("td",{"class":"mceToolbarStart"},b.createHTML("span",null,""))}}}g="mceToolbarEnd";if(j.Button){g+=" mceToolbarEndButton"}else{if(j.SplitButton){g+=" mceToolbarEndSplitButton"}else{if(j.ListBox){g+=" mceToolbarEndListBox"}}}e+=b.createHTML("td",{"class":g},b.createHTML("span",null,""));return b.createHTML("table",{id:l.id,"class":"mceToolbar"+(m["class"]?" "+m["class"]:""),cellpadding:"0",cellspacing:"0",align:l.settings.align||""},""+e+"")}});(function(b){var a=b.util.Dispatcher,c=b.each;b.create("tinymce.AddOnManager",{AddOnManager:function(){var d=this;d.items=[];d.urls={};d.lookup={};d.onAdd=new a(d)},get:function(d){return this.lookup[d]},requireLangPack:function(e){var d=b.settings;if(d&&d.language){b.ScriptLoader.add(this.urls[e]+"/langs/"+d.language+".js")}},add:function(e,d){this.items.push(d);this.lookup[e]=d;this.onAdd.dispatch(this,e,d);return d},load:function(h,e,d,g){var f=this;if(f.urls[h]){return}if(e.indexOf("/")!=0&&e.indexOf("://")==-1){e=b.baseURL+"/"+e}f.urls[h]=e.substring(0,e.lastIndexOf("/"));if(!f.lookup[h]){b.ScriptLoader.add(e,d,g)}}});b.PluginManager=new b.AddOnManager();b.ThemeManager=new b.AddOnManager()}(tinymce));(function(j){var g=j.each,d=j.extend,k=j.DOM,i=j.dom.Event,f=j.ThemeManager,b=j.PluginManager,e=j.explode,h=j.util.Dispatcher,a,c=0;j.documentBaseURL=window.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,"");if(!/[\/\\]$/.test(j.documentBaseURL)){j.documentBaseURL+="/"}j.baseURL=new j.util.URI(j.documentBaseURL).toAbsolute(j.baseURL);j.baseURI=new j.util.URI(j.baseURL);j.onBeforeUnload=new h(j);i.add(window,"beforeunload",function(l){j.onBeforeUnload.dispatch(j,l)});j.onAddEditor=new h(j);j.onRemoveEditor=new h(j);j.EditorManager=d(j,{editors:[],i18n:{},activeEditor:null,init:function(q){var n=this,p,l=j.ScriptLoader,u,o=[],m;function r(x,y,t){var v=x[y];if(!v){return}if(j.is(v,"string")){t=v.replace(/\.\w+$/,"");t=t?j.resolve(t):0;v=j.resolve(v)}return v.apply(t||this,Array.prototype.slice.call(arguments,2))}q=d({theme:"simple",language:"en"},q);n.settings=q;i.add(document,"init",function(){var s,v;r(q,"onpageload");switch(q.mode){case"exact":s=q.elements||"";if(s.length>0){g(e(s),function(x){if(k.get(x)){m=new j.Editor(x,q);o.push(m);m.render(1)}else{g(document.forms,function(y){g(y.elements,function(z){if(z.name===x){x="mce_editor_"+c++;k.setAttrib(z,"id",x);m=new j.Editor(x,q);o.push(m);m.render(1)}})})}})}break;case"textareas":case"specific_textareas":function t(y,x){return x.constructor===RegExp?x.test(y.className):k.hasClass(y,x)}g(k.select("textarea"),function(x){if(q.editor_deselector&&t(x,q.editor_deselector)){return}if(!q.editor_selector||t(x,q.editor_selector)){u=k.get(x.name);if(!x.id&&!u){x.id=x.name}if(!x.id||n.get(x.id)){x.id=k.uniqueId()}m=new j.Editor(x.id,q);o.push(m);m.render(1)}});break}if(q.oninit){s=v=0;g(o,function(x){v++;if(!x.initialized){x.onInit.add(function(){s++;if(s==v){r(q,"oninit")}})}else{s++}if(s==v){r(q,"oninit")}})}})},get:function(l){if(l===a){return this.editors}return this.editors[l]},getInstanceById:function(l){return this.get(l)},add:function(m){var l=this,n=l.editors;n[m.id]=m;n.push(m);l._setActive(m);l.onAddEditor.dispatch(l,m);if(j.adapter){j.adapter.patchEditor(m)}return m},remove:function(n){var m=this,l,o=m.editors;if(!o[n.id]){return null}delete o[n.id];for(l=0;l':"",visual_table_class:"mceItemTable",visual:1,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",apply_source_formatting:1,directionality:"ltr",forced_root_block:"p",valid_elements:"@[id|class|style|title|dir';if(F.document_base_url!=m.documentBaseURL){E.iframeHTML+=''}E.iframeHTML+='';if(m.relaxedDomain){E.iframeHTML+=''; + + bi = s.body_id || 'tinymce'; + if (bi.indexOf('=') != -1) { + bi = t.getParam('body_id', '', 'hash'); + bi = bi[t.id] || bi; + } + + bc = s.body_class || ''; + if (bc.indexOf('=') != -1) { + bc = t.getParam('body_class', '', 'hash'); + bc = bc[t.id] || ''; + } + + t.iframeHTML += ''; + + // Domain relaxing enabled, then set document domain + if (tinymce.relaxedDomain) { + // We need to write the contents here in IE since multiple writes messes up refresh button and back button + if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5)) + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; + else if (tinymce.isOpera) + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()'; + } + + // Create iframe + n = DOM.add(o.iframeContainer, 'iframe', { + id : t.id + "_ifr", + src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 + frameBorder : '0', + style : { + width : '100%', + height : h + } + }); + + t.contentAreaContainer = o.iframeContainer; + DOM.get(o.editorContainer).style.display = t.orgDisplay; + DOM.get(t.id).style.display = 'none'; + + if (!isIE || !tinymce.relaxedDomain) + t.setupIframe(); + + e = n = o = null; // Cleanup + }, + + setupIframe : function() { + var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; + + // Setup iframe body + if (!isIE || !tinymce.relaxedDomain) { + d.open(); + d.write(t.iframeHTML); + d.close(); + } + + // Design mode needs to be added here Ctrl+A will fail otherwise + if (!isIE) { + try { + if (!s.readonly) + d.designMode = 'On'; + } catch (ex) { + // Will fail on Gecko if the editor is placed in an hidden container element + // The design mode will be set ones the editor is focused + } + } + + // IE needs to use contentEditable or it will display non secure items for HTTPS + if (isIE) { + // It will not steal focus if we hide it while setting contentEditable + b = t.getBody(); + DOM.hide(b); + + if (!s.readonly) + b.contentEditable = true; + + DOM.show(b); + } + + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { + keep_values : true, + url_converter : t.convertURL, + url_converter_scope : t, + hex_colors : s.force_hex_style_colors, + class_filter : s.class_filter, + update_styles : 1, + fix_ie_paragraphs : 1, + valid_styles : s.valid_styles + }); + + t.schema = new tinymce.dom.Schema(); + + t.serializer = new tinymce.dom.Serializer(extend(s, { + valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements, + dom : t.dom, + schema : t.schema + })); + + t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); + + t.formatter = new tinymce.Formatter(this); + + // Register default formats + t.formatter.register({ + alignleft : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, + {selector : 'img,table', styles : {'float' : 'left'}} + ], + + aligncenter : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, + {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}} + ], + + alignright : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, + {selector : 'img,table', styles : {'float' : 'right'}} + ], + + alignfull : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} + ], + + bold : [ + {inline : 'strong'}, + {inline : 'span', styles : {fontWeight : 'bold'}}, + {inline : 'b'} + ], + + italic : [ + {inline : 'em'}, + {inline : 'span', styles : {fontStyle : 'italic'}}, + {inline : 'i'} + ], + + underline : [ + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, + {inline : 'u'} + ], + + strikethrough : [ + {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, + {inline : 'u'} + ], + + forecolor : {inline : 'span', styles : {color : '%value'}}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, + fontname : {inline : 'span', styles : {fontFamily : '%value'}}, + fontsize : {inline : 'span', styles : {fontSize : '%value'}}, + fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, + blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + + removeformat : [ + {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, + {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, + {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} + ] + }); + + // Register default block formats + each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { + t.formatter.register(name, {block : name, remove : 'all'}); + }); + + // Register user defined formats + t.formatter.register(t.settings.formats); + + t.undoManager = new tinymce.UndoManager(t); + + // Pass through + t.undoManager.onAdd.add(function(um, l) { + if (!l.initial) + return t.onChange.dispatch(t, l, um); + }); + + t.undoManager.onUndo.add(function(um, l) { + return t.onUndo.dispatch(t, l, um); + }); + + t.undoManager.onRedo.add(function(um, l) { + return t.onRedo.dispatch(t, l, um); + }); + + t.forceBlocks = new tinymce.ForceBlocks(t, { + forced_root_block : s.forced_root_block + }); + + t.editorCommands = new tinymce.EditorCommands(t); + + // Pass through + t.serializer.onPreProcess.add(function(se, o) { + return t.onPreProcess.dispatch(t, o, se); + }); + + t.serializer.onPostProcess.add(function(se, o) { + return t.onPostProcess.dispatch(t, o, se); + }); + + t.onPreInit.dispatch(t); + + if (!s.gecko_spellcheck) + t.getBody().spellcheck = 0; + + if (!s.readonly) + t._addEvents(); + + t.controlManager.onPostRender.dispatch(t, t.controlManager); + t.onPostRender.dispatch(t); + + if (s.directionality) + t.getBody().dir = s.directionality; + + if (s.nowrap) + t.getBody().style.whiteSpace = "nowrap"; + + if (s.custom_elements) { + function handleCustom(ed, o) { + each(explode(s.custom_elements), function(v) { + var n; + + if (v.indexOf('~') === 0) { + v = v.substring(1); + n = 'span'; + } else + n = 'div'; + + o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>'); + o.content = o.content.replace(new RegExp('', 'g'), ''); + }); + }; + + t.onBeforeSetContent.add(handleCustom); + t.onPostProcess.add(function(ed, o) { + if (o.set) + handleCustom(ed, o); + }); + } + + if (s.handle_node_change_callback) { + t.onNodeChange.add(function(ed, cm, n) { + t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); + }); + } + + if (s.save_callback) { + t.onSaveContent.add(function(ed, o) { + var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); + + if (h) + o.content = h; + }); + } + + if (s.onchange_callback) { + t.onChange.add(function(ed, l) { + t.execCallback('onchange_callback', t, l); + }); + } + + if (s.convert_newlines_to_brs) { + t.onBeforeSetContent.add(function(ed, o) { + if (o.initial) + o.content = o.content.replace(/\r?\n/g, '
      '); + }); + } + + if (s.fix_nesting && isIE) { + t.onBeforeSetContent.add(function(ed, o) { + o.content = t._fixNesting(o.content); + }); + } + + if (s.preformatted) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^\s*/, ''); + o.content = o.content.replace(/<\/pre>\s*$/, ''); + + if (o.set) + o.content = '
      ' + o.content + '
      '; + }); + } + + if (s.verify_css_classes) { + t.serializer.attribValueFilter = function(n, v) { + var s, cl; + + if (n == 'class') { + // Build regexp for classes + if (!t.classesRE) { + cl = t.dom.getClasses(); + + if (cl.length > 0) { + s = ''; + + each (cl, function(o) { + s += (s ? '|' : '') + o['class']; + }); + + t.classesRE = new RegExp('(' + s + ')', 'gi'); + } + } + + return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; + } + + return v; + }; + } + + if (s.cleanup_callback) { + t.onBeforeSetContent.add(function(ed, o) { + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + }); + + t.onPreProcess.add(function(ed, o) { + if (o.set) + t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); + + if (o.get) + t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); + }); + + t.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + + if (o.get) + o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); + }); + } + + if (s.save_callback) { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); + }); + } + + if (s.handle_event_callback) { + t.onEvent.add(function(ed, e, o) { + if (t.execCallback('handle_event_callback', e, ed, o) === false) + Event.cancel(e); + }); + } + + // Add visual aids when new contents is added + t.onSetContent.add(function() { + t.addVisual(t.getBody()); + }); + + // Remove empty contents + if (s.padd_empty_editor) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
      [\r\n]*)$/, ''); + }); + } + + if (isGecko) { + // Fix gecko link bug, when a link is placed at the end of block elements there is + // no way to move the caret behind the link. This fix adds a bogus br element after the link + function fixLinks(ed, o) { + each(ed.dom.select('a'), function(n) { + var pn = n.parentNode; + + if (ed.dom.isBlock(pn) && pn.lastChild === n) + ed.dom.add(pn, 'br', {'_mce_bogus' : 1}); + }); + }; + + t.onExecCommand.add(function(ed, cmd) { + if (cmd === 'CreateLink') + fixLinks(ed); + }); + + t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); + + if (!s.readonly) { + try { + // Design mode must be set here once again to fix a bug where + // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again + d.designMode = 'Off'; + d.designMode = 'On'; + } catch (ex) { + // Will fail on Gecko if the editor is placed in an hidden container element + // The design mode will be set ones the editor is focused + } + } + } + + // A small timeout was needed since firefox will remove. Bug: #1838304 + setTimeout(function () { + if (t.removed) + return; + + t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); + t.startContent = t.getContent({format : 'raw'}); + t.initialized = true; + + t.onInit.dispatch(t); + t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); + t.execCallback('init_instance_callback', t); + t.focus(true); + t.nodeChanged({initial : 1}); + + // Load specified content CSS last + if (s.content_css) { + tinymce.each(explode(s.content_css), function(u) { + t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); + }); + } + + // Handle auto focus + if (s.auto_focus) { + setTimeout(function () { + var ed = tinymce.get(s.auto_focus); + + ed.selection.select(ed.getBody(), 1); + ed.selection.collapse(1); + ed.getWin().focus(); + }, 100); + } + }, 1); + + e = null; + }, + + + focus : function(sf) { + var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc(); + + if (!sf) { + // Get selected control element + ieRng = t.selection.getRng(); + if (ieRng.item) { + controlElm = ieRng.item(0); + } + + // Is not content editable + if (!ce) + t.getWin().focus(); + + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + ieRng = doc.body.createControlRange(); + ieRng.addElement(controlElm); + ieRng.select(); + } + + } + + if (tinymce.activeEditor != t) { + if ((oed = tinymce.activeEditor) != null) + oed.onDeactivate.dispatch(oed, t); + + t.onActivate.dispatch(t, oed); + } + + tinymce._setActive(t); + }, + + execCallback : function(n) { + var t = this, f = t.settings[n], s; + + if (!f) + return; + + // Look through lookup + if (t.callbackLookup && (s = t.callbackLookup[n])) { + f = s.func; + s = s.scope; + } + + if (is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + t.callbackLookup = t.callbackLookup || {}; + t.callbackLookup[n] = {func : f, scope : s}; + } + + return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); + }, + + translate : function(s) { + var c = this.settings.language || 'en', i18n = tinymce.i18n; + + if (!s) + return ''; + + return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { + return i18n[c + '.' + b] || '{#' + b + '}'; + }); + }, + + getLang : function(n, dv) { + return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + getParam : function(n, dv, ty) { + var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; + + if (ty === 'hash') { + o = {}; + + if (is(v, 'string')) { + each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { + v = v.split('='); + + if (v.length > 1) + o[tr(v[0])] = tr(v[1]); + else + o[tr(v[0])] = tr(v); + }); + } else + o = v; + + return o; + } + + return v; + }, + + nodeChanged : function(o) { + var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody(); + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (t.initialized) { + o = o || {}; + n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state + + // Get parents and add them to object + o.parents = []; + t.dom.getParent(n, function(node) { + if (node.nodeName == 'BODY') + return true; + + o.parents.push(node); + }); + + t.onNodeChange.dispatch( + t, + o ? o.controlManager || t.controlManager : t.controlManager, + n, + s.isCollapsed(), + o + ); + } + }, + + addButton : function(n, s) { + var t = this; + + t.buttons = t.buttons || {}; + t.buttons[n] = s; + }, + + addCommand : function(n, f, s) { + this.execCommands[n] = {func : f, scope : s || this}; + }, + + addQueryStateHandler : function(n, f, s) { + this.queryStateCommands[n] = {func : f, scope : s || this}; + }, + + addQueryValueHandler : function(n, f, s) { + this.queryValueCommands[n] = {func : f, scope : s || this}; + }, + + addShortcut : function(pa, desc, cmd_func, sc) { + var t = this, c; + + if (!t.settings.custom_shortcuts) + return false; + + t.shortcuts = t.shortcuts || {}; + + if (is(cmd_func, 'string')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c, false, null); + }; + } + + if (is(cmd_func, 'object')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c[0], c[1], c[2]); + }; + } + + each(explode(pa), function(pa) { + var o = { + func : cmd_func, + scope : sc || this, + desc : desc, + alt : false, + ctrl : false, + shift : false + }; + + each(explode(pa, '+'), function(v) { + switch (v) { + case 'alt': + case 'ctrl': + case 'shift': + o[v] = true; + break; + + default: + o.charCode = v.charCodeAt(0); + o.keyCode = v.toUpperCase().charCodeAt(0); + } + }); + + t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; + }); + + return true; + }, + + execCommand : function(cmd, ui, val, a) { + var t = this, s = 0, o, st; + + if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) + t.focus(); + + o = {}; + t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); + if (o.terminate) + return false; + + // Command callback + if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Registred commands + if (o = t.execCommands[cmd]) { + st = o.func.call(o.scope, ui, val); + + // Fall through on true + if (st !== true) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return st; + } + } + + // Plugin commands + each(t.plugins, function(p) { + if (p.execCommand && p.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + s = 1; + return false; + } + }); + + if (s) + return true; + + // Theme commands + if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Execute global commands + if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Editor commands + if (t.editorCommands.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Browser commands + t.getDoc().execCommand(cmd, ui, val); + t.onExecCommand.dispatch(t, cmd, ui, val, a); + }, + + queryCommandState : function(cmd) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[cmd]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(cmd); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(cmd); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + queryCommandValue : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryValueCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandValue(c); + if (is(o)) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandValue(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + show : function() { + var t = this; + + DOM.show(t.getContainer()); + DOM.hide(t.id); + t.load(); + }, + + hide : function() { + var t = this, d = t.getDoc(); + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && d) + d.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + t.save(); + DOM.hide(t.getContainer()); + DOM.setStyle(t.id, 'display', t.orgDisplay); + }, + + isHidden : function() { + return !DOM.isHidden(this.id); + }, + + setProgressState : function(b, ti, o) { + this.onSetProgressState.dispatch(this, b, ti, o); + + return b; + }, + + load : function(o) { + var t = this, e = t.getElement(), h; + + if (e) { + o = o || {}; + o.load = true; + + // Double encode existing entities in the value + h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); + o.element = e; + + if (!o.no_events) + t.onLoadContent.dispatch(t, o); + + o.element = e = null; + + return h; + } + }, + + save : function(o) { + var t = this, e = t.getElement(), h, f; + + if (!e || !t.initialized) + return; + + o = o || {}; + o.save = true; + + // Add undo level will trigger onchange event + if (!o.no_events) { + t.undoManager.typing = 0; + t.undoManager.add(); + } + + o.element = e; + h = o.content = t.getContent(o); + + if (!o.no_events) + t.onSaveContent.dispatch(t, o); + + h = o.content; + + if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { + e.innerHTML = h; + + // Update hidden form element + if (f = DOM.getParent(t.id, 'form')) { + each(f.elements, function(e) { + if (e.name == t.id) { + e.value = h; + return false; + } + }); + } + } else + e.value = h; + + o.element = e = null; + + return h; + }, + + setContent : function(h, o) { + var t = this; + + o = o || {}; + o.format = o.format || 'html'; + o.set = true; + o.content = h; + + if (!o.no_events) + t.onBeforeSetContent.dispatch(t, o); + + // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content + // It will also be impossible to place the caret in the editor unless there is a BR element present + if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) { + o.content = t.dom.setHTML(t.getBody(), '
      '); + o.format = 'raw'; + } + + o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content)); + + if (o.format != 'raw' && t.settings.cleanup) { + o.getInner = true; + o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o)); + } + + if (!o.no_events) + t.onSetContent.dispatch(t, o); + + return o.content; + }, + + getContent : function(o) { + var t = this, h; + + o = o || {}; + o.format = o.format || 'html'; + o.get = true; + + if (!o.no_events) + t.onBeforeGetContent.dispatch(t, o); + + if (o.format != 'raw' && t.settings.cleanup) { + o.getInner = true; + h = t.serializer.serialize(t.getBody(), o); + } else + h = t.getBody().innerHTML; + + h = h.replace(/^\s*|\s*$/g, ''); + o.content = h; + + if (!o.no_events) + t.onGetContent.dispatch(t, o); + + return o.content; + }, + + isDirty : function() { + var t = this; + + return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty; + }, + + getContainer : function() { + var t = this; + + if (!t.container) + t.container = DOM.get(t.editorContainer || t.id + '_parent'); + + return t.container; + }, + + getContentAreaContainer : function() { + return this.contentAreaContainer; + }, + + getElement : function() { + return DOM.get(this.settings.content_element || this.id); + }, + + getWin : function() { + var t = this, e; + + if (!t.contentWindow) { + e = DOM.get(t.id + "_ifr"); + + if (e) + t.contentWindow = e.contentWindow; + } + + return t.contentWindow; + }, + + getDoc : function() { + var t = this, w; + + if (!t.contentDocument) { + w = t.getWin(); + + if (w) + t.contentDocument = w.document; + } + + return t.contentDocument; + }, + + getBody : function() { + return this.bodyElement || this.getDoc().body; + }, + + convertURL : function(u, n, e) { + var t = this, s = t.settings; + + // Use callback instead + if (s.urlconverter_callback) + return t.execCallback('urlconverter_callback', u, e, true, n); + + // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs + if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) + return u; + + // Convert to relative + if (s.relative_urls) + return t.documentBaseURI.toRelative(u); + + // Convert to absolute + u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); + + return u; + }, + + addVisual : function(e) { + var t = this, s = t.settings; + + e = e || t.getBody(); + + if (!is(t.hasVisual)) + t.hasVisual = s.visual; + + each(t.dom.select('table,a', e), function(e) { + var v; + + switch (e.nodeName) { + case 'TABLE': + v = t.dom.getAttrib(e, 'border'); + + if (!v || v == '0') { + if (t.hasVisual) + t.dom.addClass(e, s.visual_table_class); + else + t.dom.removeClass(e, s.visual_table_class); + } + + return; + + case 'A': + v = t.dom.getAttrib(e, 'name'); + + if (v) { + if (t.hasVisual) + t.dom.addClass(e, 'mceItemAnchor'); + else + t.dom.removeClass(e, 'mceItemAnchor'); + } + + return; + } + }); + + t.onVisualAid.dispatch(t, e, t.hasVisual); + }, + + remove : function() { + var t = this, e = t.getContainer(); + + t.removed = 1; // Cancels post remove event execution + t.hide(); + + t.execCallback('remove_instance_callback', t); + t.onRemove.dispatch(t); + + // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command + t.onExecCommand.listeners = []; + + tinymce.remove(t); + DOM.remove(e); + }, + + destroy : function(s) { + var t = this; + + // One time is enough + if (t.destroyed) + return; + + if (!s) { + tinymce.removeUnload(t.destroy); + tinyMCE.onBeforeUnload.remove(t._beforeUnload); + + // Manual destroy + if (t.theme && t.theme.destroy) + t.theme.destroy(); + + // Destroy controls, selection and dom + t.controlManager.destroy(); + t.selection.destroy(); + t.dom.destroy(); + + // Remove all events + + // Don't clear the window or document if content editable + // is enabled since other instances might still be present + if (!t.settings.content_editable) { + Event.clear(t.getWin()); + Event.clear(t.getDoc()); + } + + Event.clear(t.getBody()); + Event.clear(t.formElement); + } + + if (t.formElement) { + t.formElement.submit = t.formElement._mceOldSubmit; + t.formElement._mceOldSubmit = null; + } + + t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; + + if (t.selection) + t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; + + t.destroyed = 1; + }, + + // Internal functions + + _addEvents : function() { + // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset + var t = this, i, s = t.settings, dom = t.dom, lo = { + mouseup : 'onMouseUp', + mousedown : 'onMouseDown', + click : 'onClick', + keyup : 'onKeyUp', + keydown : 'onKeyDown', + keypress : 'onKeyPress', + submit : 'onSubmit', + reset : 'onReset', + contextmenu : 'onContextMenu', + dblclick : 'onDblClick', + paste : 'onPaste' // Doesn't work in all browsers yet + }; + + function eventHandler(e, o) { + var ty = e.type; + + // Don't fire events when it's removed + if (t.removed) + return; + + // Generic event handler + if (t.onEvent.dispatch(t, e, o) !== false) { + // Specific event handler + t[lo[e.fakeType || e.type]].dispatch(t, e, o); + } + }; + + // Add DOM events + each(lo, function(v, k) { + switch (k) { + case 'contextmenu': + if (tinymce.isOpera) { + // Fake contextmenu on Opera + dom.bind(t.getBody(), 'mousedown', function(e) { + if (e.ctrlKey) { + e.fakeType = 'contextmenu'; + eventHandler(e); + } + }); + } else + dom.bind(t.getBody(), k, eventHandler); + break; + + case 'paste': + dom.bind(t.getBody(), k, function(e) { + eventHandler(e); + }); + break; + + case 'submit': + case 'reset': + dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); + break; + + default: + dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); + } + }); + + dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { + t.focus(true); + }); + + + // Fixes bug where a specified document_base_uri could result in broken images + // This will also fix drag drop of images in Gecko + if (tinymce.isGecko) { + dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { + var v; + + e = e.target; + + if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src'))) + e.src = t.documentBaseURI.toAbsolute(v); + }); + } + + // Set various midas options in Gecko + if (isGecko) { + function setOpts() { + var t = this, d = t.getDoc(), s = t.settings; + + if (isGecko && !s.readonly) { + if (t._isHidden()) { + try { + if (!s.content_editable) + d.designMode = 'On'; + } catch (ex) { + // Fails if it's hidden + } + } + + try { + // Try new Gecko method + d.execCommand("styleWithCSS", 0, false); + } catch (ex) { + // Use old method + if (!t._isHidden()) + try {d.execCommand("useCSS", 0, true);} catch (ex) {} + } + + if (!s.table_inline_editing) + try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} + + if (!s.object_resizing) + try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} + } + }; + + t.onBeforeExecCommand.add(setOpts); + t.onMouseDown.add(setOpts); + } + + // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 + // WebKit can't even do simple things like selecting an image + // This also fixes so it's possible to select mceItemAnchors + if (tinymce.isWebKit) { + t.onClick.add(function(ed, e) { + e = e.target; + + // Needs tobe the setBaseAndExtend or it will fail to select floated images + if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) { + t.selection.getSel().setBaseAndExtent(e, 0, e, 1); + t.nodeChanged(); + } + }); + } + + // Add node change handlers + t.onMouseUp.add(t.nodeChanged); + //t.onClick.add(t.nodeChanged); + t.onKeyUp.add(function(ed, e) { + var c = e.keyCode; + + if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) + t.nodeChanged(); + }); + + // Add reset handler + t.onReset.add(function() { + t.setContent(t.startContent, {format : 'raw'}); + }); + + // Add shortcuts + if (s.custom_shortcuts) { + if (s.custom_undo_redo_keyboard_shortcuts) { + t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); + t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); + } + + // Add default shortcuts for gecko + t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); + t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); + t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); + + // BlockFormat shortcuts keys + for (i=1; i<=6; i++) + t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); + + t.addShortcut('ctrl+7', '', ['FormatBlock', false, '

      ']); + t.addShortcut('ctrl+8', '', ['FormatBlock', false, '

      ']); + t.addShortcut('ctrl+9', '', ['FormatBlock', false, '
      ']); + + function find(e) { + var v = null; + + if (!e.altKey && !e.ctrlKey && !e.metaKey) + return v; + + each(t.shortcuts, function(o) { + if (tinymce.isMac && o.ctrl != e.metaKey) + return; + else if (!tinymce.isMac && o.ctrl != e.ctrlKey) + return; + + if (o.alt != e.altKey) + return; + + if (o.shift != e.shiftKey) + return; + + if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { + v = o; + return false; + } + }); + + return v; + }; + + t.onKeyUp.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyPress.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyDown.add(function(ed, e) { + var o = find(e); + + if (o) { + o.func.call(o.scope); + return Event.cancel(e); + } + }); + } + + if (tinymce.isIE) { + // Fix so resize will only update the width and height attributes not the styles of an image + // It will also block mceItemNoResize items + dom.bind(t.getDoc(), 'controlselect', function(e) { + var re = t.resizeInfo, cb; + + e = e.target; + + // Don't do this action for non image elements + if (e.nodeName !== 'IMG') + return; + + if (re) + dom.unbind(re.node, re.ev, re.cb); + + if (!dom.hasClass(e, 'mceItemNoResize')) { + ev = 'resizeend'; + cb = dom.bind(e, ev, function(e) { + var v; + + e = e.target; + + if (v = dom.getStyle(e, 'width')) { + dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'width', ''); + } + + if (v = dom.getStyle(e, 'height')) { + dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'height', ''); + } + }); + } else { + ev = 'resizestart'; + cb = dom.bind(e, 'resizestart', Event.cancel, Event); + } + + re = t.resizeInfo = { + node : e, + ev : ev, + cb : cb + }; + }); + + t.onKeyDown.add(function(ed, e) { + switch (e.keyCode) { + case 8: + // Fix IE control + backspace browser bug + if (t.selection.getRng().item) { + ed.dom.remove(t.selection.getRng().item(0)); + return Event.cancel(e); + } + } + }); + + /*if (t.dom.boxModel) { + t.getBody().style.height = '100%'; + + Event.add(t.getWin(), 'resize', function(e) { + var docElm = t.getDoc().documentElement; + + docElm.style.height = (docElm.offsetHeight - 10) + 'px'; + }); + }*/ + } + + if (tinymce.isOpera) { + t.onClick.add(function(ed, e) { + Event.prevent(e); + }); + } + + // Add custom undo/redo handlers + if (s.custom_undo_redo) { + function addUndo() { + t.undoManager.typing = 0; + t.undoManager.add(); + }; + + dom.bind(t.getDoc(), 'focusout', function(e) { + if (!t.removed && t.undoManager.typing) + addUndo(); + }); + + t.onKeyUp.add(function(ed, e) { + if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) + addUndo(); + }); + + t.onKeyDown.add(function(ed, e) { + var rng, parent, bookmark; + + // IE has a really odd bug where the DOM might include an node that doesn't have + // a proper structure. If you try to access nodeValue it would throw an illegal value exception. + // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element + // after you delete contents from it. See: #3008923 + if (isIE && e.keyCode == 46) { + rng = t.selection.getRng(); + + if (rng.parentElement) { + parent = rng.parentElement(); + + // Select next word when ctrl key is used in combo with delete + if (e.ctrlKey) { + rng.moveEnd('word', 1); + rng.select(); + } + + // Delete contents + t.selection.getSel().clear(); + + // Check if we are within the same parent + if (rng.parentElement() == parent) { + bookmark = t.selection.getBookmark(); + + try { + // Update the HTML and hopefully it will remove the artifacts + parent.innerHTML = parent.innerHTML; + } catch (ex) { + // And since it's IE it can sometimes produce an unknown runtime error + } + + // Restore the caret position + t.selection.moveToBookmark(bookmark); + } + + // Block the default delete behavior since it might be broken + e.preventDefault(); + return; + } + } + + // Is caracter positon keys + if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) { + if (t.undoManager.typing) + addUndo(); + + return; + } + + if (!t.undoManager.typing) { + t.undoManager.add(); + t.undoManager.typing = 1; + } + }); + + t.onMouseDown.add(function() { + if (t.undoManager.typing) + addUndo(); + }); + } + }, + + _isHidden : function() { + var s; + + if (!isGecko) + return 0; + + // Weird, wheres that cursor selection? + s = this.selection.getSel(); + return (!s || !s.rangeCount || s.rangeCount == 0); + }, + + // Fix for bug #1867292 + _fixNesting : function(s) { + var d = [], i; + + s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) { + var e; + + // Handle end element + if (b === '/') { + if (!d.length) + return ''; + + if (c !== d[d.length - 1].tag) { + for (i=d.length - 1; i>=0; i--) { + if (d[i].tag === c) { + d[i].close = 1; + break; + } + } + + return ''; + } else { + d.pop(); + + if (d.length && d[d.length - 1].close) { + a = a + ''; + d.pop(); + } + } + } else { + // Ignore these + if (/^(br|hr|input|meta|img|link|param)$/i.test(c)) + return a; + + // Ignore closed ones + if (/\/>$/.test(a)) + return a; + + d.push({tag : c}); // Push start element + } + + return a; + }); + + // End all open tags + for (i=d.length - 1; i>=0; i--) + s += ''; + + return s; + } + }); +})(tinymce); + +(function(tinymce) { + // Added for compression purposes + var each = tinymce.each, undefined, TRUE = true, FALSE = false; + + tinymce.EditorCommands = function(editor) { + var dom = editor.dom, + selection = editor.selection, + commands = {state: {}, exec : {}, value : {}}, + settings = editor.settings, + bookmark; + + function execCommand(command, ui, value) { + var func; + + command = command.toLowerCase(); + if (func = commands.exec[command]) { + func(command, ui, value); + return TRUE; + } + + return FALSE; + }; + + function queryCommandState(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.state[command]) + return func(command); + + return -1; + }; + + function queryCommandValue(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.value[command]) + return func(command); + + return FALSE; + }; + + function addCommands(command_list, type) { + type = type || 'exec'; + + each(command_list, function(callback, command) { + each(command.toLowerCase().split(','), function(command) { + commands[type][command] = callback; + }); + }); + }; + + // Expose public methods + tinymce.extend(this, { + execCommand : execCommand, + queryCommandState : queryCommandState, + queryCommandValue : queryCommandValue, + addCommands : addCommands + }); + + // Private methods + + function execNativeCommand(command, ui, value) { + if (ui === undefined) + ui = FALSE; + + if (value === undefined) + value = null; + + return editor.getDoc().execCommand(command, ui, value); + }; + + function isFormatMatch(name) { + return editor.formatter.match(name); + }; + + function toggleFormat(name, value) { + editor.formatter.toggle(name, value ? {value : value} : undefined); + }; + + function storeSelection(type) { + bookmark = selection.getBookmark(type); + }; + + function restoreSelection() { + selection.moveToBookmark(bookmark); + }; + + // Add execCommand overrides + addCommands({ + // Ignore these, added for compatibility + 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, + + // Add undo manager logic + 'mceEndUndoLevel,mceAddUndoLevel' : function() { + editor.undoManager.add(); + }, + + 'Cut,Copy,Paste' : function(command) { + var doc = editor.getDoc(), failed; + + // Try executing the native command + try { + execNativeCommand(command); + } catch (ex) { + // Command failed + failed = TRUE; + } + + // Present alert message about clipboard access not being available + if (failed || !doc.queryCommandSupported(command)) { + if (tinymce.isGecko) { + editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { + if (state) + open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); + }); + } else + editor.windowManager.alert(editor.getLang('clipboard_no_support')); + } + }, + + // Override unlink command + unlink : function(command) { + if (selection.isCollapsed()) + selection.select(selection.getNode()); + + execNativeCommand(command); + selection.collapse(FALSE); + }, + + // Override justify commands to use the text formatter engine + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + var align = command.substring(7); + + // Remove all other alignments first + each('left,center,right,full'.split(','), function(name) { + if (align != name) + editor.formatter.remove('align' + name); + }); + + toggleFormat('align' + align); + }, + + // Override list commands to fix WebKit bug + 'InsertUnorderedList,InsertOrderedList' : function(command) { + var listElm, listParent; + + execNativeCommand(command); + + // WebKit produces lists within block elements so we need to split them + // we will replace the native list creation logic to custom logic later on + // TODO: Remove this when the list creation logic is removed + listElm = dom.getParent(selection.getNode(), 'ol,ul'); + if (listElm) { + listParent = listElm.parentNode; + + // If list is within a text block then split that block + if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { + storeSelection(); + dom.split(listParent, listElm); + restoreSelection(); + } + } + }, + + // Override commands to use the text formatter engine + 'Bold,Italic,Underline,Strikethrough' : function(command) { + toggleFormat(command); + }, + + // Override commands to use the text formatter engine + 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { + toggleFormat(command, value); + }, + + FontSize : function(command, ui, value) { + var fontClasses, fontSizes; + + // Convert font size 1-7 to styles + if (value >= 1 && value <= 7) { + fontSizes = tinymce.explode(settings.font_size_style_values); + fontClasses = tinymce.explode(settings.font_size_classes); + + if (fontClasses) + value = fontClasses[value - 1] || value; + else + value = fontSizes[value - 1] || value; + } + + toggleFormat(command, value); + }, + + RemoveFormat : function(command) { + editor.formatter.remove(command); + }, + + mceBlockQuote : function(command) { + toggleFormat('blockquote'); + }, + + FormatBlock : function(command, ui, value) { + return toggleFormat(value || 'p'); + }, + + mceCleanup : function() { + var bookmark = selection.getBookmark(); + + editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); + + selection.moveToBookmark(bookmark); + }, + + mceRemoveNode : function(command, ui, value) { + var node = value || selection.getNode(); + + // Make sure that the body node isn't removed + if (node != editor.getBody()) { + storeSelection(); + editor.dom.remove(node, TRUE); + restoreSelection(); + } + }, + + mceSelectNodeDepth : function(command, ui, value) { + var counter = 0; + + dom.getParent(selection.getNode(), function(node) { + if (node.nodeType == 1 && counter++ == value) { + selection.select(node); + return FALSE; + } + }, editor.getBody()); + }, + + mceSelectNode : function(command, ui, value) { + selection.select(value); + }, + + mceInsertContent : function(command, ui, value) { + selection.setContent(value); + }, + + mceInsertRawHTML : function(command, ui, value) { + selection.setContent('tiny_mce_marker'); + editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); + }, + + mceSetContent : function(command, ui, value) { + editor.setContent(value); + }, + + 'Indent,Outdent' : function(command) { + var intentValue, indentUnit, value; + + // Setup indent level + intentValue = settings.indentation; + indentUnit = /[a-z%]+$/i.exec(intentValue); + intentValue = parseInt(intentValue); + + if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { + each(selection.getSelectedBlocks(), function(element) { + if (command == 'outdent') { + value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); + dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); + } else + dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); + }); + } else + execNativeCommand(command); + }, + + mceRepaint : function() { + var bookmark; + + if (tinymce.isGecko) { + try { + storeSelection(TRUE); + + if (selection.getSel()) + selection.getSel().selectAllChildren(editor.getBody()); + + selection.collapse(TRUE); + restoreSelection(); + } catch (ex) { + // Ignore + } + } + }, + + mceToggleFormat : function(command, ui, value) { + editor.formatter.toggle(value); + }, + + InsertHorizontalRule : function() { + selection.setContent('
      '); + }, + + mceToggleVisualAid : function() { + editor.hasVisual = !editor.hasVisual; + editor.addVisual(); + }, + + mceReplaceContent : function(command, ui, value) { + selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + }, + + mceInsertLink : function(command, ui, value) { + var link = dom.getParent(selection.getNode(), 'a'); + + if (tinymce.is(value, 'string')) + value = {href : value}; + + if (!link) { + execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);'); + each(dom.select('a[href=javascript:mctmp(0);]'), function(link) { + dom.setAttribs(link, value); + }); + } else { + if (value.href) + dom.setAttribs(link, value); + else + editor.dom.remove(link, TRUE); + } + }, + + selectAll : function() { + var root = dom.getRoot(), rng = dom.createRng(); + + rng.setStart(root, 0); + rng.setEnd(root, root.childNodes.length); + + editor.selection.setRng(rng); + } + }); + + // Add queryCommandState overrides + addCommands({ + // Override justify commands + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + return isFormatMatch('align' + command.substring(7)); + }, + + 'Bold,Italic,Underline,Strikethrough' : function(command) { + return isFormatMatch(command); + }, + + mceBlockQuote : function() { + return isFormatMatch('blockquote'); + }, + + Outdent : function() { + var node; + + if (settings.inline_styles) { + if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + + if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + } + + return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); + }, + + 'InsertUnorderedList,InsertOrderedList' : function(command) { + return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); + } + }, 'state'); + + // Add queryCommandValue overrides + addCommands({ + 'FontSize,FontName' : function(command) { + var value = 0, parent; + + if (parent = dom.getParent(selection.getNode(), 'span')) { + if (command == 'fontsize') + value = parent.style.fontSize; + else + value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + } + + return value; + } + }, 'value'); + + // Add undo manager logic + if (settings.custom_undo_redo) { + addCommands({ + Undo : function() { + editor.undoManager.undo(); + }, + + Redo : function() { + editor.undoManager.redo(); + } + }); + } + }; +})(tinymce); +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher; + + tinymce.UndoManager = function(editor) { + var self, index = 0, data = []; + + function getContent() { + return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); + }; + + return self = { + typing : 0, + + onAdd : new Dispatcher(self), + onUndo : new Dispatcher(self), + onRedo : new Dispatcher(self), + + add : function(level) { + var i, settings = editor.settings, lastLevel; + + level = level || {}; + level.content = getContent(); + + // Add undo level if needed + lastLevel = data[index]; + if (lastLevel && lastLevel.content == level.content) { + if (index > 0 || data.length == 1) + return null; + } + + // Time to compress + if (settings.custom_undo_redo_levels) { + if (data.length > settings.custom_undo_redo_levels) { + for (i = 0; i < data.length - 1; i++) + data[i] = data[i + 1]; + + data.length--; + index = data.length; + } + } + + // Get a non intrusive normalized bookmark + level.bookmark = editor.selection.getBookmark(2, true); + + // Crop array if needed + if (index < data.length - 1) { + // Treat first level as initial + if (index == 0) + data = []; + else + data.length = index + 1; + } + + data.push(level); + index = data.length - 1; + + self.onAdd.dispatch(self, level); + editor.isNotDirty = 0; + + return level; + }, + + undo : function() { + var level, i; + + if (self.typing) { + self.add(); + self.typing = 0; + } + + if (index > 0) { + level = data[--index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.bookmark); + + self.onUndo.dispatch(self, level); + } + + return level; + }, + + redo : function() { + var level; + + if (index < data.length - 1) { + level = data[++index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.bookmark); + + self.onRedo.dispatch(self, level); + } + + return level; + }, + + clear : function() { + data = []; + index = self.typing = 0; + }, + + hasUndo : function() { + return index > 0 || self.typing; + }, + + hasRedo : function() { + return index < data.length - 1; + } + }; + }; +})(tinymce); + +(function(tinymce) { + // Shorten names + var Event = tinymce.dom.Event, + isIE = tinymce.isIE, + isGecko = tinymce.isGecko, + isOpera = tinymce.isOpera, + each = tinymce.each, + extend = tinymce.extend, + TRUE = true, + FALSE = false; + + function cloneFormats(node) { + var clone, temp, inner; + + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + if (clone) { + temp = node.cloneNode(false); + temp.appendChild(clone); + clone = temp; + } else { + clone = inner = node.cloneNode(false); + } + + clone.removeAttribute('id'); + } + } while (node = node.parentNode); + + if (clone) + return {wrapper : clone, inner : inner}; + }; + + // Checks if the selection/caret is at the end of the specified block element + function isAtEnd(rng, par) { + var rng2 = par.ownerDocument.createRange(); + + rng2.setStart(rng.endContainer, rng.endOffset); + rng2.setEndAfter(par); + + // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element + return rng2.cloneContents().textContent.length == 0; + }; + + function isEmpty(n) { + n = n.innerHTML; + + n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars + n = n.replace(/<[^>]+>/g, ''); // Remove all tags + + return n.replace(/[ \u00a0\t\r\n]+/g, '') == ''; + }; + + function splitList(selection, dom, li) { + var listBlock, block; + + if (isEmpty(li)) { + listBlock = dom.getParent(li, 'ul,ol'); + + if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { + dom.split(listBlock, li); + block = dom.create('p', 0, '
      '); + dom.replace(block, li); + selection.select(block, 1); + } + + return FALSE; + } + + return TRUE; + }; + + tinymce.create('tinymce.ForceBlocks', { + ForceBlocks : function(ed) { + var t = this, s = ed.settings, elm; + + t.editor = ed; + t.dom = ed.dom; + elm = (s.forced_root_block || 'p').toLowerCase(); + s.element = elm.toUpperCase(); + + ed.onPreInit.add(t.setup, t); + + t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); + t.rePadd = new RegExp(']+)><\\\/p>|]+)\\\/>|]+)>\\s+<\\\/p>|

      <\\\/p>||

      \\s+<\\\/p>'.replace(/p/g, elm), 'gi'); + t.reNbsp2BR1 = new RegExp(']+)>[\\s\\u00a0]+<\\\/p>|

      [\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi'); + t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi'); + t.reBR2Nbsp = new RegExp(']+)>\\s*
      \\s*<\\\/p>|

      \\s*
      \\s*<\\\/p>'.replace(/p/g, elm), 'gi'); + + function padd(ed, o) { + if (isOpera) + o.content = o.content.replace(t.reOpera, ''); + + o.content = tinymce._replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0', o.content); + + if (!isIE && !isOpera && o.set) { + // Use   instead of BR in padded paragraphs + o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
      '); + o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
      '); + } else + o.content = tinymce._replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0', o.content); + }; + + ed.onBeforeSetContent.add(padd); + ed.onPostProcess.add(padd); + + if (s.forced_root_block) { + ed.onInit.add(t.forceRoots, t); + ed.onSetContent.add(t.forceRoots, t); + ed.onBeforeGetContent.add(t.forceRoots, t); + } + }, + + setup : function() { + var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection; + + // Force root blocks when typing and when getting output + if (s.forced_root_block) { + ed.onBeforeExecCommand.add(t.forceRoots, t); + ed.onKeyUp.add(t.forceRoots, t); + ed.onPreProcess.add(t.forceRoots, t); + } + + if (s.force_br_newlines) { + // Force IE to produce BRs on enter + if (isIE) { + ed.onKeyPress.add(function(ed, e) { + var n; + + if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { + selection.setContent('
      ', {format : 'raw'}); + n = dom.get('__'); + n.removeAttribute('id'); + selection.select(n); + selection.collapse(); + return Event.cancel(e); + } + }); + } + } + + if (s.force_p_newlines) { + if (!isIE) { + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) + Event.cancel(e); + }); + } else { + // Ungly hack to for IE to preserve the formatting when you press + // enter at the end of a block element with formatted contents + // This logic overrides the browsers default logic with + // custom logic that enables us to control the output + tinymce.addUnload(function() { + t._previousFormats = 0; // Fix IE leak + }); + + ed.onKeyPress.add(function(ed, e) { + t._previousFormats = 0; + + // Clone the current formats, this will later be applied to the new block contents + if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles) + t._previousFormats = cloneFormats(ed.selection.getStart()); + }); + + ed.onKeyUp.add(function(ed, e) { + // Let IE break the element and the wrap the new caret location in the previous formats + if (e.keyCode == 13 && !e.shiftKey) { + var parent = ed.selection.getStart(), fmt = t._previousFormats; + + // Parent is an empty block + if (!parent.hasChildNodes() && fmt) { + parent = dom.getParent(parent, dom.isBlock); + + if (parent && parent.nodeName != 'LI') { + parent.innerHTML = ''; + + if (t._previousFormats) { + parent.appendChild(fmt.wrapper); + fmt.inner.innerHTML = '\uFEFF'; + } else + parent.innerHTML = '\uFEFF'; + + selection.select(parent, 1); + ed.getDoc().execCommand('Delete', false, null); + t._previousFormats = 0; + } + } + } + }); + } + + if (isGecko) { + ed.onKeyDown.add(function(ed, e) { + if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) + t.backspaceDelete(e, e.keyCode == 8); + }); + } + } + + // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 + if (tinymce.isWebKit) { + function insertBr(ed) { + var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; + + // Insert BR element + rng.insertNode(br = dom.create('br')); + + // Place caret after BR + rng.setStartAfter(br); + rng.setEndAfter(br); + selection.setRng(rng); + + // Could not place caret after BR then insert an nbsp entity and move the caret + if (selection.getSel().focusNode == br.previousSibling) { + selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); + selection.collapse(TRUE); + } + + // Create a temporary DIV after the BR and get the position as it + // seems like getPos() returns 0 for text nodes and BR elements. + dom.insertAfter(div, br); + divYPos = dom.getPos(div).y; + dom.remove(div); + + // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 + if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. + ed.getWin().scrollTo(0, divYPos); + }; + + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) { + insertBr(ed); + Event.cancel(e); + } + }); + } + + // Padd empty inline elements within block elements + // For example:

      becomes

       

      + ed.onPreProcess.add(function(ed, o) { + each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { + if (isEmpty(p)) { + each(dom.select('span,em,strong,b,i', o.node), function(n) { + if (!n.hasChildNodes()) { + n.appendChild(ed.getDoc().createTextNode('\u00a0')); + return FALSE; // Break the loop one padding is enough + } + }); + } + }); + }); + + // IE specific fixes + if (isIE) { + // Replaces IE:s auto generated paragraphs with the specified element name + if (s.element != 'P') { + ed.onKeyPress.add(function(ed, e) { + t.lastElm = selection.getNode().nodeName; + }); + + ed.onKeyUp.add(function(ed, e) { + var bl, n = selection.getNode(), b = ed.getBody(); + + if (b.childNodes.length === 1 && n.nodeName == 'P') { + n = dom.rename(n, s.element); + selection.select(n); + selection.collapse(); + ed.nodeChanged(); + } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { + bl = dom.getParent(n, 'p'); + + if (bl) { + dom.rename(bl, s.element); + ed.nodeChanged(); + } + } + }); + } + } + }, + + find : function(n, t, s) { + var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1; + + while (n = w.nextNode()) { + c++; + + // Index by node + if (t == 0 && n == s) + return c; + + // Node by index + if (t == 1 && c == s) + return n; + } + + return -1; + }, + + forceRoots : function(ed, e) { + var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF; + var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid; + + // Fix for bug #1863847 + //if (e && e.keyCode == 13) + // return TRUE; + + // Wrap non blocks into blocks + for (i = nl.length - 1; i >= 0; i--) { + nx = nl[i]; + + // Ignore internal elements + if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) { + bl = null; + continue; + } + + // Is text or non block element + if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) { + if (!bl) { + // Create new block but ignore whitespace + if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) { + // Store selection + if (si == -2 && r) { + if (!isIE || r.setStart) { + // If selection is element then mark it + if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) { + // Save the id of the selected element + eid = n.getAttribute("id"); + n.setAttribute("id", "__mce"); + } else { + // If element is inside body, might not be the case in contentEdiable mode + if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) { + so = r.startOffset; + eo = r.endOffset; + si = t.find(b, 0, r.startContainer); + ei = t.find(b, 0, r.endContainer); + } + } + } else { + // Force control range into text range + if (r.item) { + tr = d.body.createTextRange(); + tr.moveToElementText(r.item(0)); + r = tr; + } + + tr = d.body.createTextRange(); + tr.moveToElementText(b); + tr.collapse(1); + bp = tr.move('character', c) * -1; + + tr = r.duplicate(); + tr.collapse(1); + sp = tr.move('character', c) * -1; + + tr = r.duplicate(); + tr.collapse(0); + le = (tr.move('character', c) * -1) - sp; + + si = sp - bp; + ei = le; + } + } + + // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE + // See: http://support.microsoft.com/kb/829907 + bl = ed.dom.create(ed.settings.forced_root_block); + nx.parentNode.replaceChild(bl, nx); + bl.appendChild(nx); + } + } else { + if (bl.hasChildNodes()) + bl.insertBefore(nx, bl.firstChild); + else + bl.appendChild(nx); + } + } else + bl = null; // Time to create new block + } + + // Restore selection + if (si != -2) { + if (!isIE || r.setStart) { + bl = b.getElementsByTagName(ed.settings.element)[0]; + r = d.createRange(); + + // Select last location or generated block + if (si != -1) + r.setStart(t.find(b, 1, si), so); + else + r.setStart(bl, 0); + + // Select last location or generated block + if (ei != -1) + r.setEnd(t.find(b, 1, ei), eo); + else + r.setEnd(bl, 0); + + if (s) { + s.removeAllRanges(); + s.addRange(r); + } + } else { + try { + r = s.createRange(); + r.moveToElementText(b); + r.collapse(1); + r.moveStart('character', si); + r.moveEnd('character', ei); + r.select(); + } catch (ex) { + // Ignore + } + } + } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) { + // Restore the id of the selected element + if (eid) + n.setAttribute('id', eid); + else + n.removeAttribute('id'); + + // Move caret before selected element + r = d.createRange(); + r.setStartBefore(n); + r.setEndBefore(n); + se.setRng(r); + } + }, + + getParentBlock : function(n) { + var d = this.dom; + + return d.getParent(n, d.isBlock); + }, + + insertPara : function(e) { + var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; + var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; + + // If root blocks are forced then use Operas default behavior since it's really good +// Removed due to bug: #1853816 +// if (se.forced_root_block && isOpera) +// return TRUE; + + // Setup before range + rb = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + rb.setStart(s.anchorNode, s.anchorOffset); + rb.collapse(TRUE); + + // Setup after range + ra = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + ra.setStart(s.focusNode, s.focusOffset); + ra.collapse(TRUE); + + // Setup start/end points + dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; + sn = dir ? s.anchorNode : s.focusNode; + so = dir ? s.anchorOffset : s.focusOffset; + en = dir ? s.focusNode : s.anchorNode; + eo = dir ? s.focusOffset : s.anchorOffset; + + // If selection is in empty table cell + if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { + if (sn.firstChild.nodeName == 'BR') + dom.remove(sn.firstChild); // Remove BR + + // Create two new block elements + if (sn.childNodes.length == 0) { + ed.dom.add(sn, se.element, null, '
      '); + aft = ed.dom.add(sn, se.element, null, '
      '); + } else { + n = sn.innerHTML; + sn.innerHTML = ''; + ed.dom.add(sn, se.element, null, n); + aft = ed.dom.add(sn, se.element, null, '
      '); + } + + // Move caret into the last one + r = d.createRange(); + r.selectNodeContents(aft); + r.collapse(1); + ed.selection.setRng(r); + + return FALSE; + } + + // If the caret is in an invalid location in FF we need to move it into the first block + if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { + sn = en = sn.firstChild; + so = eo = 0; + rb = d.createRange(); + rb.setStart(sn, 0); + ra = d.createRange(); + ra.setStart(en, 0); + } + + // Never use body as start or end node + sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + sn = sn.nodeName == "BODY" ? sn.firstChild : sn; + en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + en = en.nodeName == "BODY" ? en.firstChild : en; + + // Get start and end blocks + sb = t.getParentBlock(sn); + eb = t.getParentBlock(en); + bn = sb ? sb.nodeName : se.element; // Get block name to create + + // Return inside list use default browser behavior + if (n = t.dom.getParent(sb, 'li,pre')) { + if (n.nodeName == 'LI') + return splitList(ed.selection, t.dom, n); + + return TRUE; + } + + // If caption or absolute layers then always generate new blocks within + if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + sb = null; + } + + // If caption or absolute layers then always generate new blocks within + if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + eb = null; + } + + // Use P instead + if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { + bn = se.element; + sb = eb = null; + } + + // Setup new before and after blocks + bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); + aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); + + // Remove id from after clone + aft.removeAttribute('id'); + + // Is header and cursor is at the end, then force paragraph under + if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) + aft = ed.dom.create(se.element); + + // Find start chop node + n = sc = sn; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + sc = n; + } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); + + // Find end chop node + n = ec = en; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + ec = n; + } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); + + // Place first chop part into before block element + if (sc.nodeName == bn) + rb.setStart(sc, 0); + else + rb.setStartBefore(sc); + + rb.setEnd(sn, so); + bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Place secnd chop part within new block element + try { + ra.setEndAfter(ec); + } catch(ex) { + //console.debug(s.focusNode, s.focusOffset); + } + + ra.setStart(en, eo); + aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Create range around everything + r = d.createRange(); + if (!sc.previousSibling && sc.parentNode.nodeName == bn) { + r.setStartBefore(sc.parentNode); + } else { + if (rb.startContainer.nodeName == bn && rb.startOffset == 0) + r.setStartBefore(rb.startContainer); + else + r.setStart(rb.startContainer, rb.startOffset); + } + + if (!ec.nextSibling && ec.parentNode.nodeName == bn) + r.setEndAfter(ec.parentNode); + else + r.setEnd(ra.endContainer, ra.endOffset); + + // Delete and replace it with new block elements + r.deleteContents(); + + if (isOpera) + ed.getWin().scrollTo(0, vp.y); + + // Never wrap blocks in blocks + if (bef.firstChild && bef.firstChild.nodeName == bn) + bef.innerHTML = bef.firstChild.innerHTML; + + if (aft.firstChild && aft.firstChild.nodeName == bn) + aft.innerHTML = aft.firstChild.innerHTML; + + // Padd empty blocks + if (isEmpty(bef)) + bef.innerHTML = '
      '; + + function appendStyles(e, en) { + var nl = [], nn, n, i; + + e.innerHTML = ''; + + // Make clones of style elements + if (se.keep_styles) { + n = en; + do { + // We only want style specific elements + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { + nn = n.cloneNode(FALSE); + dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique + nl.push(nn); + } + } while (n = n.parentNode); + } + + // Append style elements to aft + if (nl.length > 0) { + for (i = nl.length - 1, nn = e; i >= 0; i--) + nn = nn.appendChild(nl[i]); + + // Padd most inner style element + nl[0].innerHTML = isOpera ? ' ' : '
      '; // Extra space for Opera so that the caret can move there + return nl[0]; // Move caret to most inner element + } else + e.innerHTML = isOpera ? ' ' : '
      '; // Extra space for Opera so that the caret can move there + }; + + // Fill empty afterblook with current style + if (isEmpty(aft)) + car = appendStyles(aft, en); + + // Opera needs this one backwards for older versions + if (isOpera && parseFloat(opera.version()) < 9.5) { + r.insertNode(bef); + r.insertNode(aft); + } else { + r.insertNode(aft); + r.insertNode(bef); + } + + // Normalize + aft.normalize(); + bef.normalize(); + + function first(n) { + return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n; + }; + + // Move cursor and scroll into view + r = d.createRange(); + r.selectNodeContents(isGecko ? first(car || aft) : car || aft); + r.collapse(1); + s.removeAllRanges(); + s.addRange(r); + + // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs + y = ed.dom.getPos(aft).y; + ch = aft.clientHeight; + + // Is element within viewport + if (y < vp.y || y + ch > vp.y + vp.h) { + ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks + //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight)); + } + + return FALSE; + }, + + backspaceDelete : function(e, bs) { + var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker; + + // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651 + if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) { + walker = new tinymce.dom.TreeWalker(sc.lastChild, sc); + + // Walk the dom backwards until we find a text node + for (n = sc.lastChild; n; n = walker.prev()) { + if (n.nodeType == 3) { + r.setStart(n, n.nodeValue.length); + r.collapse(true); + se.setRng(r); + return; + } + } + } + + // The caret sometimes gets stuck in Gecko if you delete empty paragraphs + // This workaround removes the element by hand and moves the caret to the previous element + if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { + if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { + // Find previous block element + n = sc; + while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; + + if (n) { + if (sc != b.firstChild) { + // Find last text node + w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); + while (tn = w.nextNode()) + n = tn; + + // Place caret at the end of last text node + r = ed.getDoc().createRange(); + r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); + r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); + se.setRng(r); + + // Remove the target container + ed.dom.remove(sc); + } + + return Event.cancel(e); + } + } + } + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; + + tinymce.create('tinymce.ControlManager', { + ControlManager : function(ed, s) { + var t = this, i; + + s = s || {}; + t.editor = ed; + t.controls = {}; + t.onAdd = new tinymce.util.Dispatcher(t); + t.onPostRender = new tinymce.util.Dispatcher(t); + t.prefix = s.prefix || ed.id + '_'; + t._cls = {}; + + t.onPostRender.add(function() { + each(t.controls, function(c) { + c.postRender(); + }); + }); + }, + + get : function(id) { + return this.controls[this.prefix + id] || this.controls[id]; + }, + + setActive : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setActive(s); + + return c; + }, + + setDisabled : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setDisabled(s); + + return c; + }, + + add : function(c) { + var t = this; + + if (c) { + t.controls[c.id] = c; + t.onAdd.dispatch(c, t); + } + + return c; + }, + + createControl : function(n) { + var c, t = this, ed = t.editor; + + each(ed.plugins, function(p) { + if (p.createControl) { + c = p.createControl(n, t); + + if (c) + return false; + } + }); + + switch (n) { + case "|": + case "separator": + return t.createSeparator(); + } + + if (!c && ed.buttons && (c = ed.buttons[n])) + return t.createButton(n, c); + + return t.add(c); + }, + + createDropMenu : function(id, s, cc) { + var t = this, ed = t.editor, c, bm, v, cls; + + s = extend({ + 'class' : 'mceDropDown', + constrain : ed.settings.constrain_menus + }, s); + + s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; + if (v = ed.getParam('skin_variant')) + s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); + + id = t.prefix + id; + cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; + c = t.controls[id] = new cls(id, s); + c.onAddItem.add(function(c, o) { + var s = o.settings; + + s.title = ed.getLang(s.title, s.title); + + if (!s.onclick) { + s.onclick = function(v) { + if (s.cmd) + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + }); + + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createListBox : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + if (ed.settings.use_native_selects) + c = new tinymce.ui.NativeListBox(id, s); + else { + cls = cc || t._cls.listbox || tinymce.ui.ListBox; + c = new cls(id, s); + } + + t.controls[id] = c; + + // Fix focus problem in Safari + if (tinymce.isWebKit) { + c.onPostRender.add(function(c, n) { + // Store bookmark on mousedown + Event.add(n, 'mousedown', function() { + ed.bookmark = ed.selection.getBookmark(1); + }); + + // Restore on focus, since it might be lost + Event.add(n, 'focus', function() { + ed.selection.moveToBookmark(ed.bookmark); + ed.bookmark = null; + }); + }); + } + + if (c.hideMenu) + ed.onMouseDown.add(c.hideMenu, c); + + return t.add(c); + }, + + createButton : function(id, s, cc) { + var t = this, ed = t.editor, o, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.label = ed.translate(s.label); + s.scope = s.scope || ed; + + if (!s.onclick && !s.menu_button) { + s.onclick = function() { + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + unavailable_prefix : ed.getLang('unavailable', ''), + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + if (s.menu_button) { + cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; + c = new cls(id, s); + ed.onMouseDown.add(c.hideMenu, c); + } else { + cls = t._cls.button || tinymce.ui.Button; + c = new cls(id, s); + } + + return t.add(c); + }, + + createMenuButton : function(id, s, cc) { + s = s || {}; + s.menu_button = 1; + + return this.createButton(id, s, cc); + }, + + createSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; + c = t.add(new cls(id, s)); + ed.onMouseDown.add(c.hideMenu, c); + + return c; + }, + + createColorSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls, bm; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + if (tinymce.isIE) + bm = ed.selection.getBookmark(1); + + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + 'menu_class' : ed.getParam('skin') + 'Skin', + scope : s.scope, + more_colors_title : ed.getLang('more_colors') + }, s); + + id = t.prefix + id; + cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; + c = new cls(id, s); + ed.onMouseDown.add(c.hideMenu, c); + + // Remove the menu element when the editor is removed + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createToolbar : function(id, s, cc) { + var c, t = this, cls; + + id = t.prefix + id; + cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; + c = new cls(id, s); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createSeparator : function(cc) { + var cls = cc || this._cls.separator || tinymce.ui.Separator; + + return new cls(); + }, + + setControlType : function(n, c) { + return this._cls[n.toLowerCase()] = c; + }, + + destroy : function() { + each(this.controls, function(c) { + c.destroy(); + }); + + this.controls = null; + } + }); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + tinymce.create('tinymce.WindowManager', { + WindowManager : function(ed) { + var t = this; + + t.editor = ed; + t.onOpen = new Dispatcher(t); + t.onClose = new Dispatcher(t); + t.params = {}; + t.features = {}; + }, + + open : function(s, p) { + var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; + + // Default some options + s = s || {}; + p = p || {}; + sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window + sh = isOpera ? vp.h : screen.height; + s.name = s.name || 'mc_' + new Date().getTime(); + s.width = parseInt(s.width || 320); + s.height = parseInt(s.height || 240); + s.resizable = true; + s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); + s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); + p.inline = false; + p.mce_width = s.width; + p.mce_height = s.height; + p.mce_auto_focus = s.auto_focus; + + if (mo) { + if (isIE) { + s.center = true; + s.help = false; + s.dialogWidth = s.width + 'px'; + s.dialogHeight = s.height + 'px'; + s.scroll = s.scrollbars || false; + } + } + + // Build features string + each(s, function(v, k) { + if (tinymce.is(v, 'boolean')) + v = v ? 'yes' : 'no'; + + if (!/^(name|url)$/.test(k)) { + if (isIE && mo) + f += (f ? ';' : '') + k + ':' + v; + else + f += (f ? ',' : '') + k + '=' + v; + } + }); + + t.features = s; + t.params = p; + t.onOpen.dispatch(t, s, p); + + u = s.url || s.file; + u = tinymce._addVer(u); + + try { + if (isIE && mo) { + w = 1; + window.showModalDialog(u, window, f); + } else + w = window.open(u, s.name, f); + } catch (ex) { + // Ignore + } + + if (!w) + alert(t.editor.getLang('popup_blocked')); + }, + + close : function(w) { + w.close(); + this.onClose.dispatch(this); + }, + + createInstance : function(cl, a, b, c, d, e) { + var f = tinymce.resolve(cl); + + return new f(a, b, c, d, e); + }, + + confirm : function(t, cb, s, w) { + w = w || window; + + cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); + }, + + alert : function(tx, cb, s, w) { + var t = this; + + w = w || window; + w.alert(t._decode(t.editor.getLang(tx, tx))); + + if (cb) + cb.call(s || t); + }, + + resizeBy : function(dw, dh, win) { + win.resizeBy(dw, dh); + }, + + // Internal functions + + _decode : function(s) { + return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); + } + }); +}(tinymce)); +(function(tinymce) { + function CommandManager() { + var execCommands = {}, queryStateCommands = {}, queryValueCommands = {}; + + function add(collection, cmd, func, scope) { + if (typeof(cmd) == 'string') + cmd = [cmd]; + + tinymce.each(cmd, function(cmd) { + collection[cmd.toLowerCase()] = {func : func, scope : scope}; + }); + }; + + tinymce.extend(this, { + add : function(cmd, func, scope) { + add(execCommands, cmd, func, scope); + }, + + addQueryStateHandler : function(cmd, func, scope) { + add(queryStateCommands, cmd, func, scope); + }, + + addQueryValueHandler : function(cmd, func, scope) { + add(queryValueCommands, cmd, func, scope); + }, + + execCommand : function(scope, cmd, ui, value, args) { + if (cmd = execCommands[cmd.toLowerCase()]) { + if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false) + return true; + } + }, + + queryCommandValue : function() { + if (cmd = queryValueCommands[cmd.toLowerCase()]) + return cmd.func.call(scope || cmd.scope, ui, value, args); + }, + + queryCommandState : function() { + if (cmd = queryStateCommands[cmd.toLowerCase()]) + return cmd.func.call(scope || cmd.scope, ui, value, args); + } + }); + }; + + tinymce.GlobalCommands = new CommandManager(); +})(tinymce); +(function(tinymce) { + tinymce.Formatter = function(ed) { + var formats = {}, + each = tinymce.each, + dom = ed.dom, + selection = ed.selection, + TreeWalker = tinymce.dom.TreeWalker, + rangeUtils = new tinymce.dom.RangeUtils(dom), + isValid = ed.schema.isValid, + isBlock = dom.isBlock, + forcedRootBlock = ed.settings.forced_root_block, + nodeIndex = dom.nodeIndex, + INVISIBLE_CHAR = '\uFEFF', + MCE_ATTR_RE = /^(src|href|style)$/, + FALSE = false, + TRUE = true, + undefined, + pendingFormats = {apply : [], remove : []}; + + function isArray(obj) { + return obj instanceof Array; + }; + + function getParents(node, selector) { + return dom.getParents(node, selector, dom.getRoot()); + }; + + function isCaretNode(node) { + return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); + }; + + // Public functions + + function get(name) { + return name ? formats[name] : formats; + }; + + function register(name, format) { + if (name) { + if (typeof(name) !== 'string') { + each(name, function(format, name) { + register(name, format); + }); + } else { + // Force format into array and add it to internal collection + format = format.length ? format : [format]; + + each(format, function(format) { + // Set deep to false by default on selector formats this to avoid removing + // alignment on images inside paragraphs when alignment is changed on paragraphs + if (format.deep === undefined) + format.deep = !format.selector; + + // Default to true + if (format.split === undefined) + format.split = !format.selector || format.inline; + + // Default to true + if (format.remove === undefined && format.selector && !format.inline) + format.remove = 'none'; + + // Mark format as a mixed format inline + block level + if (format.selector && format.inline) { + format.mixed = true; + format.block_expand = true; + } + + // Split classes if needed + if (typeof(format.classes) === 'string') + format.classes = format.classes.split(/\s+/); + }); + + formats[name] = format; + } + } + }; + + function apply(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, rng, i; + + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node; + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1 || container.nodeValue === "") { + container = container.nodeType == 1 ? container.childNodes[offset] : container; + + // Might fail if the offset is behind the last element in it's container + if (container) { + walker = new TreeWalker(container, container.parentNode); + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + rng.setStart(node, 0); + break; + } + } + } + } + + return rng; + }; + + function setElementFormat(elm, fmt) { + fmt = fmt || format; + + if (elm) { + each(fmt.styles, function(value, name) { + dom.setStyle(elm, name, replaceVars(value, vars)); + }); + + each(fmt.attributes, function(value, name) { + dom.setAttrib(elm, name, replaceVars(value, vars)); + }); + + each(fmt.classes, function(value) { + value = replaceVars(value, vars); + + if (!dom.hasClass(elm, value)) + dom.addClass(elm, value); + }); + } + }; + + function applyRngStyle(rng) { + var newWrappers = [], wrapName, wrapElm; + + // Setup wrapper element + wrapName = format.inline || format.block; + wrapElm = dom.create(wrapName); + setElementFormat(wrapElm); + + rangeUtils.walk(rng, function(nodes) { + var currentWrapElm; + + function process(node) { + var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; + + // Stop wrapping on br elements + if (isEq(nodeName, 'br')) { + currentWrapElm = 0; + + // Remove any br elements when we wrap things + if (format.block) + dom.remove(node); + + return; + } + + // If node is wrapper type + if (format.wrapper && matchNode(node, name, vars)) { + currentWrapElm = 0; + return; + } + + // Can we rename the block + if (format.block && !format.wrapper && isTextBlock(nodeName)) { + node = dom.rename(node, wrapName); + setElementFormat(node); + newWrappers.push(node); + currentWrapElm = 0; + return; + } + + // Handle selector patterns + if (format.selector) { + // Look for matching formats + each(formatList, function(format) { + if (dom.is(node, format.selector) && !isCaretNode(node)) { + setElementFormat(node, format); + found = true; + } + }); + + // Continue processing if a selector match wasn't found and a inline element is defined + if (!format.inline || found) { + currentWrapElm = 0; + return; + } + } + + // Is it valid to wrap this item + if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) { + // Start wrapping + if (!currentWrapElm) { + // Wrap the node + currentWrapElm = wrapElm.cloneNode(FALSE); + node.parentNode.insertBefore(currentWrapElm, node); + newWrappers.push(currentWrapElm); + } + + currentWrapElm.appendChild(node); + } else { + // Start a new wrapper for possible children + currentWrapElm = 0; + + each(tinymce.grep(node.childNodes), process); + + // End the last wrapper + currentWrapElm = 0; + } + }; + + // Process siblings from range + each(nodes, process); + }); + + // Cleanup + each(newWrappers, function(node) { + var childCount; + + function getChildCount(node) { + var count = 0; + + each(node.childNodes, function(node) { + if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) + count++; + }); + + return count; + }; + + function mergeStyles(node) { + var child, clone; + + each(node.childNodes, function(node) { + if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { + child = node; + return FALSE; // break loop + } + }); + + // If child was found and of the same type as the current node + if (child && matchName(child, format)) { + clone = child.cloneNode(FALSE); + setElementFormat(clone); + + dom.replace(clone, node, TRUE); + dom.remove(child, 1); + } + + return clone || node; + }; + + childCount = getChildCount(node); + + // Remove empty nodes + if (childCount === 0) { + dom.remove(node, 1); + return; + } + + if (format.inline || format.wrapper) { + // Merges the current node with it's children of similar type to reduce the number of elements + if (!format.exact && childCount === 1) + node = mergeStyles(node); + + // Remove/merge children + each(formatList, function(format) { + // Merge all children of similar type will move styles from child to parent + // this: text + // will become: text + each(dom.select(format.inline, node), function(child) { + removeFormat(format, vars, child, format.exact ? child : null); + }); + }); + + // Remove child if direct parent is of same type + if (matchNode(node.parentNode, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + + // Look for parent with similar style format + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function(parent) { + if (matchNode(parent, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + }); + } + + // Merge next and previous siblings if they are similar texttext becomes texttext + if (node) { + node = mergeSiblings(getNonWhiteSpaceSibling(node), node); + node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); + } + } + }); + }; + + if (format) { + if (node) { + rng = dom.createRng(); + + rng.setStartBefore(node); + rng.setEndAfter(node); + + applyRngStyle(expandRng(rng, formatList)); + } else { + if (!selection.isCollapsed() || !format.inline) { + // Apply formatting to selection + bookmark = selection.getBookmark(); + applyRngStyle(expandRng(selection.getRng(TRUE), formatList)); + + selection.moveToBookmark(bookmark); + selection.setRng(moveStart(selection.getRng(TRUE))); + ed.nodeChanged(); + } else + performCaretAction('apply', name, vars); + } + } + }; + + function remove(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, i, rng; + + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node, nodes, tmpNode; + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) { + container = container.parentNode; + offset = nodeIndex(container) + 1; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1) + walker.next(); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', null, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + }; + + // Merges the styles for each node + function process(node) { + var children, i, l; + + // Grab the children first since the nodelist might be changed + children = tinymce.grep(node.childNodes); + + // Process current node + for (i = 0, l = formatList.length; i < l; i++) { + if (removeFormat(formatList[i], vars, node, node)) + break; + } + + // Process the children + if (format.deep) { + for (i = 0, l = children.length; i < l; i++) + process(children[i]); + } + }; + + function findFormatRoot(container) { + var formatRoot; + + // Find format root + each(getParents(container.parentNode).reverse(), function(parent) { + var format; + + // Find format root element + if (!formatRoot && parent.id != '_start' && parent.id != '_end') { + // Is the node matching the format we are looking for + format = matchNode(parent, name, vars); + if (format && format.split !== false) + formatRoot = parent; + } + }); + + return formatRoot; + }; + + function wrapAndSplit(format_root, container, target, split) { + var parent, clone, lastClone, firstClone, i, formatRootParent; + + // Format root found then clone formats and split it + if (format_root) { + formatRootParent = format_root.parentNode; + + for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { + clone = parent.cloneNode(FALSE); + + for (i = 0; i < formatList.length; i++) { + if (removeFormat(formatList[i], vars, clone, clone)) { + clone = 0; + break; + } + } + + // Build wrapper node + if (clone) { + if (lastClone) + clone.appendChild(lastClone); + + if (!firstClone) + firstClone = clone; + + lastClone = clone; + } + } + + // Never split block elements if the format is mixed + if (split && (!format.mixed || !isBlock(format_root))) + container = dom.split(format_root, container); + + // Wrap container in cloned formats + if (lastClone) { + target.parentNode.insertBefore(lastClone, target); + firstClone.appendChild(target); + } + } + + return container; + }; + + function splitToFormatRoot(container) { + return wrapAndSplit(findFormatRoot(container), container, container, true); + }; + + function unwrap(start) { + var node = dom.get(start ? '_start' : '_end'), + out = node[start ? 'firstChild' : 'lastChild']; + + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) + out = out[start ? 'firstChild' : 'lastChild']; + + dom.remove(node, true); + + return out; + }; + + function removeRngStyle(rng) { + var startContainer, endContainer; + + rng = expandRng(rng, formatList, TRUE); + + if (format.split) { + startContainer = getContainer(rng, TRUE); + endContainer = getContainer(rng); + + if (startContainer != endContainer) { + // Wrap start/end nodes in span element since these might be cloned/moved + startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'}); + + // Split start/end + splitToFormatRoot(startContainer); + splitToFormatRoot(endContainer); + + // Unwrap start/end to get real elements again + startContainer = unwrap(TRUE); + endContainer = unwrap(); + } else + startContainer = endContainer = splitToFormatRoot(startContainer); + + // Update range positions since they might have changed after the split operations + rng.startContainer = startContainer.parentNode; + rng.startOffset = nodeIndex(startContainer); + rng.endContainer = endContainer.parentNode; + rng.endOffset = nodeIndex(endContainer) + 1; + } + + // Remove items between start/end + rangeUtils.walk(rng, function(nodes) { + each(nodes, function(node) { + process(node); + }); + }); + }; + + // Handle node + if (node) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + removeRngStyle(rng); + return; + } + + if (!selection.isCollapsed() || !format.inline) { + bookmark = selection.getBookmark(); + removeRngStyle(selection.getRng(TRUE)); + selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" and need to move the start into the next text node + if (match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + + ed.nodeChanged(); + } else + performCaretAction('remove', name, vars); + }; + + function toggle(name, vars, node) { + if (match(name, vars, node)) + remove(name, vars, node); + else + apply(name, vars, node); + }; + + function matchNode(node, name, vars, similar) { + var formatList = get(name), format, i, classes; + + function matchItems(node, format, item_name) { + var key, value, items = format[item_name], i; + + // Check all items + if (items) { + // Non indexed object + if (items.length === undefined) { + for (key in items) { + if (items.hasOwnProperty(key)) { + if (item_name === 'attributes') + value = dom.getAttrib(node, key); + else + value = getStyle(node, key); + + if (similar && !value && !format.exact) + return; + + if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) + return; + } + } + } else { + // Only one match needed for indexed arrays + for (i = 0; i < items.length; i++) { + if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) + return format; + } + } + } + + return format; + }; + + if (formatList && node) { + // Check each format in list + for (i = 0; i < formatList.length; i++) { + format = formatList[i]; + + // Name name, attributes, styles and classes + if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { + // Match classes + if (classes = format.classes) { + for (i = 0; i < classes.length; i++) { + if (!dom.hasClass(node, classes[i])) + return; + } + } + + return format; + } + } + } + }; + + function match(name, vars, node) { + var startNode, i; + + function matchParents(node) { + // Find first node with similar format settings + node = dom.getParent(node, function(node) { + return !!matchNode(node, name, vars, true); + }); + + // Do an exact check on the similar format element + return matchNode(node, name, vars); + }; + + // Check specified node + if (node) + return matchParents(node); + + // Check pending formats + if (selection.isCollapsed()) { + for (i = pendingFormats.apply.length - 1; i >= 0; i--) { + if (pendingFormats.apply[i].name == name) + return true; + } + + for (i = pendingFormats.remove.length - 1; i >= 0; i--) { + if (pendingFormats.remove[i].name == name) + return false; + } + + return matchParents(selection.getNode()); + } + + // Check selected node + node = selection.getNode(); + if (matchParents(node)) + return TRUE; + + // Check start node if it's different + startNode = selection.getStart(); + if (startNode != node) { + if (matchParents(startNode)) + return TRUE; + } + + return FALSE; + }; + + function matchAll(names, vars) { + var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; + + // If the selection is collapsed then check pending formats + if (selection.isCollapsed()) { + for (ni = 0; ni < names.length; ni++) { + // If the name is to be removed, then stop it from being added + for (i = pendingFormats.remove.length - 1; i >= 0; i--) { + name = names[ni]; + + if (pendingFormats.remove[i].name == name) { + checkedMap[name] = true; + break; + } + } + } + + // If the format is to be applied + for (i = pendingFormats.apply.length - 1; i >= 0; i--) { + for (ni = 0; ni < names.length; ni++) { + name = names[ni]; + + if (!checkedMap[name] && pendingFormats.apply[i].name == name) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + } + } + + // Check start of selection for formats + startElement = selection.getStart(); + dom.getParent(startElement, function(node) { + var i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + + if (!checkedMap[name] && matchNode(node, name, vars)) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + }); + + return matchedFormatNames; + }; + + function canApply(name) { + var formatList = get(name), startNode, parents, i, x, selector; + + if (formatList) { + startNode = selection.getStart(); + parents = getParents(startNode); + + for (x = formatList.length - 1; x >= 0; x--) { + selector = formatList[x].selector; + + // Format is not selector based, then always return TRUE + if (!selector) + return TRUE; + + for (i = parents.length - 1; i >= 0; i--) { + if (dom.is(parents[i], selector)) + return TRUE; + } + } + } + + return FALSE; + }; + + // Expose to public + tinymce.extend(this, { + get : get, + register : register, + apply : apply, + remove : remove, + toggle : toggle, + match : match, + matchAll : matchAll, + matchNode : matchNode, + canApply : canApply + }); + + // Private functions + + function matchName(node, format) { + // Check for inline match + if (isEq(node, format.inline)) + return TRUE; + + // Check for block match + if (isEq(node, format.block)) + return TRUE; + + // Check for selector match + if (format.selector) + return dom.is(node, format.selector); + }; + + function isEq(str1, str2) { + str1 = str1 || ''; + str2 = str2 || ''; + + str1 = '' + (str1.nodeName || str1); + str2 = '' + (str2.nodeName || str2); + + return str1.toLowerCase() == str2.toLowerCase(); + }; + + function getStyle(node, name) { + var styleVal = dom.getStyle(node, name); + + // Force the format to hex + if (name == 'color' || name == 'backgroundColor') + styleVal = dom.toHex(styleVal); + + // Opera will return bold as 700 + if (name == 'fontWeight' && styleVal == 700) + styleVal = 'bold'; + + return '' + styleVal; + }; + + function replaceVars(value, vars) { + if (typeof(value) != "string") + value = value(vars); + else if (vars) { + value = value.replace(/%(\w+)/g, function(str, name) { + return vars[name] || str; + }); + } + + return value; + }; + + function isWhiteSpaceNode(node) { + return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue); + }; + + function wrap(node, name, attrs) { + var wrapper = dom.create(name, attrs); + + node.parentNode.insertBefore(wrapper, node); + wrapper.appendChild(node); + + return wrapper; + }; + + function expandRng(rng, format, remove) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, sibling, lastIdx; + + // This function walks up the tree if there is no siblings before/after the node + function findParentContainer(container, child_name, sibling_name, root) { + var parent, child; + + root = root || dom.getRoot(); + + for (;;) { + // Check if we can move up are we at root level or body level + parent = container.parentNode; + + // Stop expanding on block elements or root depending on format + if (parent == root || (!format[0].block_expand && isBlock(parent))) + return container; + + for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) { + if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) + return container; + + if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) + return container; + } + + container = container.parentNode; + } + + return container; + }; + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { + lastIdx = startContainer.childNodes.length - 1; + startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { + lastIdx = endContainer.childNodes.length - 1; + endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; + + if (endContainer.nodeType == 3) + endOffset = endContainer.nodeValue.length; + } + + // Exclude bookmark nodes if possible + if (isBookmarkNode(startContainer.parentNode)) + startContainer = startContainer.parentNode; + + if (isBookmarkNode(startContainer)) + startContainer = startContainer.nextSibling || startContainer; + + if (isBookmarkNode(endContainer.parentNode)) + endContainer = endContainer.parentNode; + + if (isBookmarkNode(endContainer)) + endContainer = endContainer.previousSibling || endContainer; + + // Move start/end point up the tree if the leaves are sharp and if we are in different containers + // Example * becomes !: !

      *texttext*

      ! + // This will reduce the number of wrapper elements that needs to be created + // Move start point up the tree + if (format[0].inline || format[0].block_expand) { + startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); + endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); + } + + // Expand start/end container to matching selector + if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { + function findSelectorEndPoint(container, sibling_name) { + var parents, i, y; + + if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) + container = container[sibling_name]; + + parents = getParents(container); + for (i = 0; i < parents.length; i++) { + for (y = 0; y < format.length; y++) { + if (dom.is(parents[i], format[y].selector)) + return parents[i]; + } + } + + return container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); + endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); + } + + // Expand start/end container to matching block element or text node + if (format[0].block || format[0].selector) { + function findBlockEndPoint(container, sibling_name, sibling_name2) { + var node; + + // Expand to block of similar type + if (!format[0].wrapper) + node = dom.getParent(container, format[0].block); + + // Expand to first wrappable block element or any block element + if (!node) + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); + + // Exclude inner lists from wrapping + if (node && format[0].wrapper) + node = getParents(node, 'ul,ol').reverse()[0] || node; + + // Didn't find a block element look for first/last wrappable element + if (!node) { + node = container; + + while (node[sibling_name] && !isBlock(node[sibling_name])) { + node = node[sibling_name]; + + // Break on BR but include it will be removed later on + // we can't remove it now since we need to check if it can be wrapped + if (isEq(node, 'br')) + break; + } + } + + return node || container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findBlockEndPoint(startContainer, 'previousSibling'); + endContainer = findBlockEndPoint(endContainer, 'nextSibling'); + + // Non block element then try to expand up the leaf + if (format[0].block) { + if (!isBlock(startContainer)) + startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); + + if (!isBlock(endContainer)) + endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); + } + } + + // Setup index for startContainer + if (startContainer.nodeType == 1) { + startOffset = nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + // Setup index for endContainer + if (endContainer.nodeType == 1) { + endOffset = nodeIndex(endContainer) + 1; + endContainer = endContainer.parentNode; + } + + // Return new range like object + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + } + + function removeFormat(format, vars, node, compare_node) { + var i, attrs, stylesModified; + + // Check if node matches format + if (!matchName(node, format)) + return FALSE; + + // Should we compare with format attribs and styles + if (format.remove != 'all') { + // Remove styles + each(format.styles, function(value, name) { + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(getStyle(compare_node, name), value)) + dom.setStyle(node, name, ''); + + stylesModified = 1; + }); + + // Remove style attribute if it's empty + if (stylesModified && dom.getAttrib(node, 'style') == '') { + node.removeAttribute('style'); + node.removeAttribute('_mce_style'); + } + + // Remove attributes + each(format.attributes, function(value, name) { + var valueOut; + + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { + // Keep internal classes + if (name == 'class') { + value = dom.getAttrib(node, name); + if (value) { + // Build new class value where everything is removed except the internal prefixed classes + valueOut = ''; + each(value.split(/\s+/), function(cls) { + if (/mce\w+/.test(cls)) + valueOut += (valueOut ? ' ' : '') + cls; + }); + + // We got some internal classes left + if (valueOut) { + dom.setAttrib(node, name, valueOut); + return; + } + } + } + + // IE6 has a bug where the attribute doesn't get removed correctly + if (name == "class") + node.removeAttribute('className'); + + // Remove mce prefixed attributes + if (MCE_ATTR_RE.test(name)) + node.removeAttribute('_mce_' + name); + + node.removeAttribute(name); + } + }); + + // Remove classes + each(format.classes, function(value) { + value = replaceVars(value, vars); + + if (!compare_node || dom.hasClass(compare_node, value)) + dom.removeClass(node, value); + }); + + // Check for non internal attributes + attrs = dom.getAttribs(node); + for (i = 0; i < attrs.length; i++) { + if (attrs[i].nodeName.indexOf('_') !== 0) + return FALSE; + } + } + + // Remove the inline child if it's empty for example or + if (format.remove != 'none') { + removeNode(node, format); + return TRUE; + } + }; + + function removeNode(node, format) { + var parentNode = node.parentNode, rootBlockElm; + + if (format.block) { + if (!forcedRootBlock) { + function find(node, next, inc) { + node = getNonWhiteSpaceSibling(node, next, inc); + + return !node || (node.nodeName == 'BR' || isBlock(node)); + }; + + // Append BR elements if needed before we remove the block + if (isBlock(node) && !isBlock(parentNode)) { + if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) + node.insertBefore(dom.create('br'), node.firstChild); + + if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) + node.appendChild(dom.create('br')); + } + } else { + // Wrap the block in a forcedRootBlock if we are at the root of document + if (parentNode == dom.getRoot()) { + if (!format.list_block || !isEq(node, format.list_block)) { + each(tinymce.grep(node.childNodes), function(node) { + if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { + if (!rootBlockElm) + rootBlockElm = wrap(node, forcedRootBlock); + else + rootBlockElm.appendChild(node); + } else + rootBlockElm = 0; + }); + } + } + } + } + + // Never remove nodes that isn't the specified inline element if a selector is specified too + if (format.selector && format.inline && !isEq(format.inline, node)) + return; + + dom.remove(node, 1); + }; + + function getNonWhiteSpaceSibling(node, next, inc) { + if (node) { + next = next ? 'nextSibling' : 'previousSibling'; + + for (node = inc ? node : node[next]; node; node = node[next]) { + if (node.nodeType == 1 || !isWhiteSpaceNode(node)) + return node; + } + } + }; + + function isBookmarkNode(node) { + return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark'; + }; + + function mergeSiblings(prev, next) { + var marker, sibling, tmpSibling; + + function compareElements(node1, node2) { + // Not the same name + if (node1.nodeName != node2.nodeName) + return FALSE; + + function getAttribs(node) { + var attribs = {}; + + each(dom.getAttribs(node), function(attr) { + var name = attr.nodeName.toLowerCase(); + + // Don't compare internal attributes or style + if (name.indexOf('_') !== 0 && name !== 'style') + attribs[name] = dom.getAttrib(node, name); + }); + + return attribs; + }; + + function compareObjects(obj1, obj2) { + var value, name; + + for (name in obj1) { + // Obj1 has item obj2 doesn't have + if (obj1.hasOwnProperty(name)) { + value = obj2[name]; + + // Obj2 doesn't have obj1 item + if (value === undefined) + return FALSE; + + // Obj2 item has a different value + if (obj1[name] != value) + return FALSE; + + // Delete similar value + delete obj2[name]; + } + } + + // Check if obj 2 has something obj 1 doesn't have + for (name in obj2) { + // Obj2 has item obj1 doesn't have + if (obj2.hasOwnProperty(name)) + return FALSE; + } + + return TRUE; + }; + + // Attribs are not the same + if (!compareObjects(getAttribs(node1), getAttribs(node2))) + return FALSE; + + // Styles are not the same + if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) + return FALSE; + + return TRUE; + }; + + // Check if next/prev exists and that they are elements + if (prev && next) { + function findElementSibling(node, sibling_name) { + for (sibling = node; sibling; sibling = sibling[sibling_name]) { + if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) + return node; + + if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) + return sibling; + } + + return node; + }; + + // If previous sibling is empty then jump over it + prev = findElementSibling(prev, 'previousSibling'); + next = findElementSibling(next, 'nextSibling'); + + // Compare next and previous nodes + if (compareElements(prev, next)) { + // Append nodes between + for (sibling = prev.nextSibling; sibling && sibling != next;) { + tmpSibling = sibling; + sibling = sibling.nextSibling; + prev.appendChild(tmpSibling); + } + + // Remove next node + dom.remove(next); + + // Move children into prev node + each(tinymce.grep(next.childNodes), function(node) { + prev.appendChild(node); + }); + + return prev; + } + } + + return next; + }; + + function isTextBlock(name) { + return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); + }; + + function getContainer(rng, start) { + var container, offset, lastIdx; + + container = rng[start ? 'startContainer' : 'endContainer']; + offset = rng[start ? 'startOffset' : 'endOffset']; + + if (container.nodeType == 1) { + lastIdx = container.childNodes.length - 1; + + if (!start && offset) + offset--; + + container = container.childNodes[offset > lastIdx ? lastIdx : offset]; + } + + return container; + }; + + function performCaretAction(type, name, vars) { + var i, currentPendingFormats = pendingFormats[type], + otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply']; + + function hasPending() { + return pendingFormats.apply.length || pendingFormats.remove.length; + }; + + function resetPending() { + pendingFormats.apply = []; + pendingFormats.remove = []; + }; + + function perform(caret_node) { + // Apply pending formats + each(pendingFormats.apply.reverse(), function(item) { + apply(item.name, item.vars, caret_node); + }); + + // Remove pending formats + each(pendingFormats.remove.reverse(), function(item) { + remove(item.name, item.vars, caret_node); + }); + + dom.remove(caret_node, 1); + resetPending(); + }; + + // Check if it already exists then ignore it + for (i = currentPendingFormats.length - 1; i >= 0; i--) { + if (currentPendingFormats[i].name == name) + return; + } + + currentPendingFormats.push({name : name, vars : vars}); + + // Check if it's in the other type, then remove it + for (i = otherPendingFormats.length - 1; i >= 0; i--) { + if (otherPendingFormats[i].name == name) + otherPendingFormats.splice(i, 1); + } + + // Pending apply or remove formats + if (hasPending()) { + ed.getDoc().execCommand('FontName', false, 'mceinline'); + pendingFormats.lastRng = selection.getRng(); + + // IE will convert the current word + each(dom.select('font,span'), function(node) { + var bookmark; + + if (isCaretNode(node)) { + bookmark = selection.getBookmark(); + perform(node); + selection.moveToBookmark(bookmark); + ed.nodeChanged(); + } + }); + + // Only register listeners once if we need to + if (!pendingFormats.isListening && hasPending()) { + pendingFormats.isListening = true; + + each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) { + ed[event].addToTop(function(ed, e) { + // Do we have pending formats and is the selection moved has moved + if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) { + each(dom.select('font,span'), function(node) { + var textNode, rng; + + // Look for marker + if (isCaretNode(node)) { + textNode = node.firstChild; + + if (textNode) { + perform(node); + + rng = dom.createRng(); + rng.setStart(textNode, textNode.nodeValue.length); + rng.setEnd(textNode, textNode.nodeValue.length); + selection.setRng(rng); + ed.nodeChanged(); + } else + dom.remove(node); + } + }); + + // Always unbind and clear pending styles on keyup + if (e.type == 'keyup' || e.type == 'mouseup') + resetPending(); + } + }); + }); + } + } + }; + }; +})(tinymce); + +tinymce.onAddEditor.add(function(tinymce, ed) { + var filters, fontSizes, dom, settings = ed.settings; + + if (settings.inline_styles) { + fontSizes = tinymce.explode(settings.font_size_style_values); + + function replaceWithSpan(node, styles) { + tinymce.each(styles, function(value, name) { + if (value) + dom.setStyle(node, name, value); + }); + + dom.rename(node, 'span'); + }; + + filters = { + font : function(dom, node) { + replaceWithSpan(node, { + backgroundColor : node.style.backgroundColor, + color : node.color, + fontFamily : node.face, + fontSize : fontSizes[parseInt(node.size) - 1] + }); + }, + + u : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'underline' + }); + }, + + strike : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'line-through' + }); + } + }; + + function convert(editor, params) { + dom = editor.dom; + + if (settings.convert_fonts_to_spans) { + tinymce.each(dom.select('font,u,strike', params.node), function(node) { + filters[node.nodeName.toLowerCase()](ed.dom, node); + }); + } + }; + + ed.onPreProcess.add(convert); + + ed.onInit.add(function() { + ed.selection.onSetContent.add(convert); + }); + } +}); + diff --git a/web/js/globals/tinymce/utils/editable_selects.js b/web/js/globals/tinymce/utils/editable_selects.js new file mode 100644 index 0000000..fd943c0 --- /dev/null +++ b/web/js/globals/tinymce/utils/editable_selects.js @@ -0,0 +1,70 @@ +/** + * editable_selects.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +var TinyMCE_EditableSelects = { + editSelectElm : null, + + init : function() { + var nl = document.getElementsByTagName("select"), i, d = document, o; + + for (i=0; i'; + h += ' '; + + return h; +} + +function updateColor(img_id, form_element_id) { + document.getElementById(img_id).style.backgroundColor = document.forms[0].elements[form_element_id].value; +} + +function setBrowserDisabled(id, state) { + var img = document.getElementById(id); + var lnk = document.getElementById(id + "_link"); + + if (lnk) { + if (state) { + lnk.setAttribute("realhref", lnk.getAttribute("href")); + lnk.removeAttribute("href"); + tinyMCEPopup.dom.addClass(img, 'disabled'); + } else { + if (lnk.getAttribute("realhref")) + lnk.setAttribute("href", lnk.getAttribute("realhref")); + + tinyMCEPopup.dom.removeClass(img, 'disabled'); + } + } +} + +function getBrowserHTML(id, target_form_element, type, prefix) { + var option = prefix + "_" + type + "_browser_callback", cb, html; + + cb = tinyMCEPopup.getParam(option, tinyMCEPopup.getParam("file_browser_callback")); + + if (!cb) + return ""; + + html = ""; + html += ''; + html += ' '; + + return html; +} + +function openBrowser(img_id, target_form_element, type, option) { + var img = document.getElementById(img_id); + + if (img.className != "mceButtonDisabled") + tinyMCEPopup.openBrowser(target_form_element, type, option); +} + +function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { + if (!form_obj || !form_obj.elements[field_name]) + return; + + var sel = form_obj.elements[field_name]; + + var found = false; + for (var i=0; i parseInt(v)) + st = this.mark(f, n); + } + } + + return st; + }, + + hasClass : function(n, c, d) { + return new RegExp('\\b' + c + (d ? '[0-9]+' : '') + '\\b', 'g').test(n.className); + }, + + getNum : function(n, c) { + c = n.className.match(new RegExp('\\b' + c + '([0-9]+)\\b', 'g'))[0]; + c = c.replace(/[^0-9]/g, ''); + + return c; + }, + + addClass : function(n, c, b) { + var o = this.removeClass(n, c); + n.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c; + }, + + removeClass : function(n, c) { + c = n.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)"), ' '); + return n.className = c != ' ' ? c : ''; + }, + + tags : function(f, s) { + return f.getElementsByTagName(s); + }, + + mark : function(f, n) { + var s = this.settings; + + this.addClass(n, s.invalid_cls); + this.markLabels(f, n, s.invalid_cls); + + return false; + }, + + markLabels : function(f, n, ic) { + var nl, i; + + nl = this.tags(f, "label"); + for (i=0; i