├── .gitignore ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── autoload.php ├── composer.json ├── phpdoc.dist.xml ├── phpunit.xml.dist ├── sample ├── composer.json └── index.php ├── src ├── About.php ├── Activity.php ├── ActivityDefinition.php ├── ActivityProfile.php ├── Agent.php ├── AgentAccount.php ├── AgentProfile.php ├── ArraySetterTrait.php ├── AsVersionTrait.php ├── Attachment.php ├── ComparableInterface.php ├── Context.php ├── ContextActivities.php ├── Document.php ├── Extensions.php ├── FromJSONTrait.php ├── Group.php ├── JSONParseErrorException.php ├── LRSInterface.php ├── LRSResponse.php ├── LanguageMap.php ├── Map.php ├── Person.php ├── RemoteLRS.php ├── Result.php ├── Score.php ├── SignatureComparisonTrait.php ├── State.php ├── Statement.php ├── StatementBase.php ├── StatementRef.php ├── StatementTargetInterface.php ├── StatementsResult.php ├── SubStatement.php ├── Util.php ├── Verb.php ├── Version.php └── VersionableInterface.php └── tests ├── AboutTest.php ├── ActivityDefinitionTest.php ├── ActivityProfileTest.php ├── ActivityTest.php ├── AgentAccountTest.php ├── AgentProfileTest.php ├── AgentTest.php ├── AsVersionTraitTest.php ├── AttachmentTest.php ├── ContextActivitiesTest.php ├── ContextTest.php ├── DocumentTest.php ├── ExtensionsTest.php ├── GroupTest.php ├── ISO8601Test.php ├── JSONParseErrorExceptionTest.php ├── LRSResponseTest.php ├── LanguageMapTest.php ├── MapTest.php ├── PersonTest.php ├── RemoteLRSTest.php ├── ResultTest.php ├── ScoreTest.php ├── SignatureComparisonTraitTest.php ├── StateTest.php ├── StatementBaseTest.php ├── StatementRefTest.php ├── StatementTest.php ├── StatementVariationsTest.php ├── SubStatementTest.php ├── TestCompareWithSignatureTrait.php ├── UtilTest.php ├── VerbTest.php ├── VersionTest.php ├── config ├── bootstrap.php ├── config.dist.php └── config.travis-ci.php ├── files └── image.jpg └── keys └── travis ├── cacert.pem └── privkey.pem /.gitignore: -------------------------------------------------------------------------------- 1 | tests/config/config.php 2 | 3 | # Third party libraries 4 | phpdoc.xml 5 | phpunit.xml 6 | php.ini 7 | 8 | # buildables 9 | vendor 10 | composer.lock 11 | sample/vendor 12 | sample/composer.lock 13 | doc/api 14 | doc/coverage 15 | 16 | # wild 17 | todo.md 18 | 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | php: 4 | - 7.0 5 | - 5.6 6 | - 5.5 7 | - hhvm 8 | before_script: 9 | - cp tests/config/config.travis-ci.php tests/config/config.php 10 | - composer install --dev 11 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | TinCanPHP 2 | Copyright 2014 Rustici Software 3 | 4 | This product includes software developed at 5 | Rustici Software (http://www.rusticisoftware.com/). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A PHP library for implementing the Experience API (Tin Can API). 2 | 3 | [![Build Status](https://travis-ci.org/RusticiSoftware/TinCanPHP.png)](https://travis-ci.org/RusticiSoftware/TinCanPHP) 4 | [![Latest Stable Version](https://poser.pugx.org/rusticisoftware/tincan/v/stable)](https://packagist.org/packages/rusticisoftware/tincan) 5 | [![License](https://poser.pugx.org/rusticisoftware/tincan/license)](https://packagist.org/packages/rusticisoftware/tincan) 6 | [![Total Downloads](https://poser.pugx.org/rusticisoftware/tincan/downloads)](https://packagist.org/packages/rusticisoftware/tincan) 7 | 8 | For hosted API documentation, basic usage instructions, supported version listing, etc. visit the main project website at: 9 | 10 | http://rusticisoftware.github.io/TinCanPHP/ 11 | 12 | For more information about the Experience API visit: 13 | 14 | http://experienceapi.com/ 15 | 16 | Requires PHP 5.5 or later. (If you must run something older you should look at the 0.x release series or the PHP_5_2 branch.) 17 | 18 | ### Installation 19 | 20 | TinCanPHP is available via [Composer](http://getcomposer.org). 21 | 22 | ``` 23 | php composer.phar require rusticisoftware/tincan:@stable 24 | ``` 25 | 26 | When not using Composer, require the autoloader: 27 | 28 | ```php 29 | require 'path/to/TinCan/autoload.php'; 30 | ``` 31 | 32 | ### Testing 33 | 34 | Tests are implemented using the latest stable version of PHPUnit. It will be installed when using Composer. Configure the LRS endpoint and credentials by copying the `tests/config/config.dist.php` to `tests/config/config.php` then setting the values for your LRS. 35 | 36 | Once configured run: 37 | 38 | ``` 39 | vendor/bin/phpunit 40 | ``` 41 | 42 | ### API Doc Generation 43 | 44 | Documentation can be output using [phpDocumentor2](http://phpdoc.org). It will be installed when using Composer. To generate documentation: 45 | 46 | ``` 47 | vendor/bin/phpdoc 48 | ``` 49 | 50 | From the root of the repository after running `php composer.phar update`. Documentation will be output to `doc/api`. 51 | 52 | If you do not have the default timezone set in your `php.ini` file you can create one in the base of the repo and use the `PHPRC` environment variable to point to it. Use something like: 53 | 54 | ``` 55 | export PHPRC="/path/to/repos/TinCanPHP/php.ini" 56 | ``` 57 | 58 | And set the timezone in that file using: 59 | 60 | ``` 61 | [PHP] 62 | 63 | date.timezone = "US/Central" 64 | ``` 65 | 66 | ### Certificate Generation 67 | 68 | These instructions are for creating the requisite public/private key pair and certificate on a Mac with OpenSSL installed. See and . 69 | 70 | Generate a private key (which contains a public key) without a password (not recommended): 71 | 72 | openssl genrsa -out privkey.pem 2048 73 | 74 | To generate a private key with a password: 75 | 76 | openssl genrsa -des3 -out privkey.pem 2048 77 | 78 | Create a certificate signing request: 79 | 80 | openssl req -new -key privkey.pem -out cert.csr 81 | 82 | To create a self signed certificate (as opposed to one signed by a CA), primarily for testing purposes: 83 | 84 | openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095 85 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | TinCanPHP by Rustici Software and Community 4 | 5 | doc/api 6 | 7 | 8 | doc/api 9 | 10 | 11 | src 12 | 13 | 14 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | tests 17 | tests/config 18 | tests/files 19 | tests/keys 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | src 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /sample/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "rusticisoftware/tincan": "~0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /sample/index.php: -------------------------------------------------------------------------------- 1 | queryStatements(['limit' => 2]); 12 | print_r($response); 13 | 14 | -------------------------------------------------------------------------------- /src/About.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 32 | } 33 | 34 | if (! isset($this->version)) { 35 | $this->setVersion(array()); 36 | } 37 | if (! isset($this->extensions)) { 38 | $this->setExtensions(array()); 39 | } 40 | } 41 | 42 | public function setVersion($value) { $this->version = $value; return $this; } 43 | public function getVersion() { return $this->version; } 44 | 45 | public function setExtensions($value) { 46 | if (! $value instanceof Extensions) { 47 | $value = new Extensions($value); 48 | } 49 | 50 | $this->extensions = $value; 51 | 52 | return $this; 53 | } 54 | public function getExtensions() { return $this->extensions; } 55 | } 56 | -------------------------------------------------------------------------------- /src/Activity.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 36 | } 37 | } 38 | 39 | public function getObjectType() { return $this->objectType; } 40 | 41 | // FEATURE: check IRI? 42 | public function setId($value) { $this->id = $value; return $this; } 43 | public function getId() { return $this->id; } 44 | 45 | public function setDefinition($value) { 46 | if (! $value instanceof ActivityDefinition && is_array($value)) { 47 | $value = new ActivityDefinition($value); 48 | } 49 | 50 | $this->definition = $value; 51 | 52 | return $this; 53 | } 54 | public function getDefinition() { return $this->definition; } 55 | } 56 | -------------------------------------------------------------------------------- /src/ActivityDefinition.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 42 | } 43 | 44 | foreach ( 45 | [ 46 | 'name', 47 | 'description', 48 | 'extensions', 49 | ] as $k 50 | ) { 51 | $method = 'set' . ucfirst($k); 52 | 53 | if (! isset($this->$k)) { 54 | $this->$method(array()); 55 | } 56 | } 57 | } 58 | 59 | // FEATURE: check URI? 60 | public function setType($value) { $this->type = $value; return $this; } 61 | public function getType() { return $this->type; } 62 | 63 | public function setName($value) { 64 | if (! $value instanceof LanguageMap) { 65 | $value = new LanguageMap($value); 66 | } 67 | 68 | $this->name = $value; 69 | 70 | return $this; 71 | } 72 | public function getName() { return $this->name; } 73 | 74 | public function setDescription($value) { 75 | if (! $value instanceof LanguageMap) { 76 | $value = new LanguageMap($value); 77 | } 78 | 79 | $this->description = $value; 80 | 81 | return $this; 82 | } 83 | public function getDescription() { return $this->description; } 84 | 85 | public function setMoreInfo($value) { $this->moreInfo = $value; return $this; } 86 | public function getMoreInfo() { return $this->moreInfo; } 87 | 88 | public function setExtensions($value) { 89 | if (! $value instanceof Extensions) { 90 | $value = new Extensions($value); 91 | } 92 | 93 | $this->extensions = $value; 94 | 95 | return $this; 96 | } 97 | public function getExtensions() { return $this->extensions; } 98 | 99 | public function setInteractionType($value) { $this->interactionType = $value; return $this; } 100 | public function getInteractionType() { return $this->interactionType; } 101 | public function setCorrectResponsesPattern($value) { $this->correctResponsesPattern = $value; return $this; } 102 | public function getCorrectResponsesPattern() { return $this->correctResponsesPattern; } 103 | 104 | // TODO: make these arrays of InteractionComponents 105 | public function setChoices($value) { $this->choices = $value; return $this; } 106 | public function getChoices() { return $this->choices; } 107 | public function setScale($value) { $this->scale = $value; return $this; } 108 | public function getScale() { return $this->scale; } 109 | public function setSource($value) { $this->source = $value; return $this; } 110 | public function getSource() { return $this->source; } 111 | public function setTarget($value) { $this->target = $value; return $this; } 112 | public function getTarget() { return $this->target; } 113 | public function setSteps($value) { $this->steps = $value; return $this; } 114 | public function getSteps() { return $this->steps; } 115 | } 116 | -------------------------------------------------------------------------------- /src/ActivityProfile.php: -------------------------------------------------------------------------------- 1 | activity = $value; 30 | 31 | return $this; 32 | } 33 | public function getActivity() { return $this->activity; } 34 | } 35 | -------------------------------------------------------------------------------- /src/Agent.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 36 | } 37 | } 38 | 39 | public function asVersion($version) { 40 | $result = array( 41 | 'objectType' => $this->objectType 42 | ); 43 | 44 | if (isset($this->name)) { 45 | $result['name'] = $this->name; 46 | } 47 | 48 | // 49 | // only one of these, note that if 'account' has been set 50 | // but is returned empty then no IFI will be included 51 | // 52 | if (isset($this->account)) { 53 | $versioned_acct = $this->account->asVersion($version); 54 | if (! empty($versioned_acct)) { 55 | $result['account'] = $versioned_acct; 56 | } 57 | } 58 | elseif (isset($this->mbox_sha1sum)) { 59 | $result['mbox_sha1sum'] = $this->mbox_sha1sum; 60 | } 61 | elseif (isset($this->mbox)) { 62 | $result['mbox'] = $this->mbox; 63 | } 64 | elseif (isset($this->openid)) { 65 | $result['openid'] = $this->openid; 66 | } 67 | 68 | return $result; 69 | } 70 | 71 | public function isIdentified() { 72 | return (isset($this->mbox) || isset($this->mbox_sha1sum) || isset($this->openid) || isset($this->account)); 73 | } 74 | 75 | // 76 | // having multiple IFIs set shouldn't cause a problem here 77 | // so long as all of their values match their counterparts 78 | // 79 | // we could allow for multiple IFIs in the `this` object and 80 | // ignore them missing in the signature but discussion ruled 81 | // against that need since the serialization via asVersion 82 | // shouldn't result in that ever for a valid statement 83 | // 84 | public function compareWithSignature($fromSig) { 85 | // 86 | // mbox and mbox_sha1sum are a special case where they can be 87 | // equal but have to be transformed for comparison, check them 88 | // first and short circuit 89 | // 90 | if (isset($this->mbox) && isset($fromSig->mbox_sha1sum)) { 91 | if ($this->getMbox_sha1sum() === $fromSig->mbox_sha1sum) { 92 | return array('success' => true, 'reason' => null); 93 | } 94 | 95 | return array('success' => false, 'reason' => 'Comparison of this.mbox to signature.mbox_sha1sum failed: no match'); 96 | } 97 | elseif (isset($fromSig->mbox) && isset($this->mbox_sha1sum)) { 98 | if ($fromSig->getMbox_sha1sum() === $this->mbox_sha1sum) { 99 | return array('success' => true, 'reason' => null); 100 | } 101 | 102 | return array('success' => false, 'reason' => 'Comparison of this.mbox_sha1sum to signature.mbox failed: no match'); 103 | } 104 | 105 | foreach (array('mbox', 'mbox_sha1sum', 'openid') as $property) { 106 | if (isset($this->$property) || isset($fromSig->$property)) { 107 | if ($this->$property !== $fromSig->$property) { 108 | return array('success' => false, 'reason' => "Comparison of $property failed: value is not the same"); 109 | } 110 | } 111 | } 112 | if (isset($this->account) || isset($fromSig->account)) { 113 | if (! isset($fromSig->account)) { 114 | return array('success' => false, 'reason' => "Comparison of account failed: value not in signature"); 115 | } 116 | if (! isset($this->account)) { 117 | return array('success' => false, 'reason' => "Comparison of account failed: value not in this"); 118 | } 119 | 120 | $acctResult = $this->account->compareWithSignature($fromSig->account); 121 | if (! $acctResult['success']) { 122 | return array('success' => false, 'reason' => "Comparison of account failed: " . $acctResult['reason']); 123 | } 124 | } 125 | 126 | return array('success' => true, 'reason' => null); 127 | } 128 | 129 | public function getObjectType() { return $this->objectType; } 130 | 131 | public function setName($value) { $this->name = $value; return $this; } 132 | public function getName() { return $this->name; } 133 | 134 | public function setMbox($value) { 135 | if (isset($value) && (! (stripos($value, 'mailto:') === 0))) { 136 | $value = 'mailto:' . $value; 137 | } 138 | $this->mbox = $value; 139 | return $this; 140 | } 141 | public function getMbox() { return $this->mbox; } 142 | 143 | public function setMbox_sha1sum($value) { $this->mbox_sha1sum = $value; return $this; } 144 | public function getMbox_sha1sum() { 145 | if (isset($this->mbox_sha1sum)) { 146 | return $this->mbox_sha1sum; 147 | } 148 | 149 | if (isset($this->mbox)) { 150 | return sha1($this->mbox); 151 | } 152 | } 153 | public function setOpenid($value) { $this->openid = $value; return $this; } 154 | public function getOpenid() { return $this->openid; } 155 | 156 | public function setAccount($value) { 157 | if (! $value instanceof AgentAccount && is_array($value)) { 158 | $value = new AgentAccount($value); 159 | } 160 | 161 | $this->account = $value; 162 | 163 | return $this; 164 | } 165 | public function getAccount() { return $this->account; } 166 | } 167 | -------------------------------------------------------------------------------- /src/AgentAccount.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 32 | } 33 | } 34 | 35 | public function setName($value) { $this->name = $value; return $this; } 36 | public function getName() { return $this->name; } 37 | public function setHomePage($value) { $this->homePage = $value; return $this; } 38 | public function getHomePage() { return $this->homePage; } 39 | } 40 | -------------------------------------------------------------------------------- /src/AgentProfile.php: -------------------------------------------------------------------------------- 1 | agent = $value; 35 | 36 | return $this; 37 | } 38 | public function getAgent() { return $this->agent; } 39 | } 40 | -------------------------------------------------------------------------------- /src/ArraySetterTrait.php: -------------------------------------------------------------------------------- 1 | $v) { 24 | $method = 'set' . ucfirst($k); 25 | if (isset($options[$k]) && method_exists($this, $method)) { 26 | $this->$method($options[$k]); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AsVersionTrait.php: -------------------------------------------------------------------------------- 1 | $value) { 37 | // 38 | // skip properties that start with an underscore to allow 39 | // storing information that isn't included in statement 40 | // structure etc. (see Attachment.content for example) 41 | // 42 | if (strpos($property, '_') === 0) { 43 | continue; 44 | } 45 | 46 | if ($value instanceof VersionableInterface) { 47 | $value = $value->asVersion($version); 48 | } 49 | elseif (is_array($value) && !empty($value)) { 50 | $tmp_value = array(); 51 | foreach ($value as $element) { 52 | if ($element instanceof VersionableInterface) { 53 | array_push($tmp_value, $element->asVersion($version)); 54 | } 55 | else { 56 | array_push($tmp_value, $element); 57 | } 58 | } 59 | $value = $tmp_value; 60 | } 61 | 62 | if (isset($value) && (!is_array($value) || !empty($value))) { 63 | $result[$property] = $value; 64 | } 65 | } 66 | 67 | if (method_exists($this, '_asVersion')) { 68 | $this->_asVersion($result, $version); 69 | } 70 | 71 | return $result; 72 | } 73 | 74 | /** 75 | * Prevent external mutation 76 | * 77 | * @param string $property 78 | * @param mixed $value 79 | * @throws DomainException 80 | */ 81 | final public function __set($property, $value) { 82 | throw new DomainException(__CLASS__ . ' is immutable'); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Attachment.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 40 | 41 | if (isset($arg['content'])) { 42 | $this->setContent($arg['content']); 43 | } 44 | } 45 | 46 | foreach ( 47 | [ 48 | 'display', 49 | 'description', 50 | ] as $k 51 | ) { 52 | $method = 'set' . ucfirst($k); 53 | 54 | if (! isset($this->$k)) { 55 | $this->$method(array()); 56 | } 57 | } 58 | } 59 | 60 | public function setUsageType($value) { $this->usageType = $value; return $this; } 61 | public function getUsageType() { return $this->usageType; } 62 | 63 | public function setDisplay($value) { 64 | if (! $value instanceof LanguageMap) { 65 | $value = new LanguageMap($value); 66 | } 67 | 68 | $this->display = $value; 69 | 70 | return $this; 71 | } 72 | public function getDisplay() { return $this->display; } 73 | 74 | public function setDescription($value) { 75 | if (! $value instanceof LanguageMap) { 76 | $value = new LanguageMap($value); 77 | } 78 | 79 | $this->description = $value; 80 | 81 | return $this; 82 | } 83 | public function getDescription() { return $this->description; } 84 | 85 | public function setContentType($value) { $this->contentType = $value; return $this; } 86 | public function getContentType() { return $this->contentType; } 87 | public function setLength($value) { $this->length = $value; return $this; } 88 | public function getLength() { return $this->length; } 89 | public function setSha2($value) { $this->sha2 = $value; return $this; } 90 | public function getSha2() { return $this->sha2; } 91 | public function setFileUrl($value) { $this->fileUrl = $value; return $this; } 92 | public function getFileUrl() { return $this->fileUrl; } 93 | 94 | public function setContent($value) { 95 | $this->_content = $value; 96 | $this->setLength(strlen($value)); 97 | $this->setSha2(hash("sha256", $value)); 98 | return $this; 99 | } 100 | public function getContent() { return $this->_content; } 101 | public function hasContent() { return isset($this->_content); } 102 | } 103 | -------------------------------------------------------------------------------- /src/ComparableInterface.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 41 | } 42 | 43 | foreach ( 44 | [ 45 | 'contextActivities', 46 | 'extensions', 47 | ] as $k 48 | ) { 49 | $method = 'set' . ucfirst($k); 50 | 51 | if (! isset($this->$k)) { 52 | $this->$method(array()); 53 | } 54 | } 55 | } 56 | 57 | public function setRegistration($value) { 58 | if (isset($value) && ! preg_match(Util::UUID_REGEX, $value)) { 59 | throw new InvalidArgumentException('arg1 must be a UUID'); 60 | } 61 | $this->registration = $value; 62 | return $this; 63 | } 64 | public function getRegistration() { return $this->registration; } 65 | 66 | public function setInstructor($value) { 67 | if (! ($value instanceof Agent || $value instanceof Group) && is_array($value)) { 68 | if (isset($value['objectType']) && $value['objectType'] === "Group") { 69 | $value = new Group($value); 70 | } 71 | else { 72 | $value = new Agent($value); 73 | } 74 | } 75 | 76 | $this->instructor = $value; 77 | 78 | return $this; 79 | } 80 | public function getInstructor() { return $this->instructor; } 81 | 82 | public function setTeam($value) { 83 | if (! $value instanceof Group && is_array($value)) { 84 | $value = new Group($value); 85 | } 86 | 87 | $this->team = $value; 88 | 89 | return $this; 90 | } 91 | public function getTeam() { return $this->team; } 92 | 93 | public function setContextActivities($value) { 94 | if (! $value instanceof ContextActivities) { 95 | $value = new ContextActivities($value); 96 | } 97 | 98 | $this->contextActivities = $value; 99 | 100 | return $this; 101 | } 102 | public function getContextActivities() { return $this->contextActivities; } 103 | 104 | public function setRevision($value) { $this->revision = $value; return $this; } 105 | public function getRevision() { return $this->revision; } 106 | public function setPlatform($value) { $this->platform = $value; return $this; } 107 | public function getPlatform() { return $this->platform; } 108 | public function setLanguage($value) { $this->language = $value; return $this; } 109 | public function getLanguage() { return $this->language; } 110 | 111 | public function setStatement($value) { 112 | if (! $value instanceof StatementRef && is_array($value)) { 113 | $value = new StatementRef($value); 114 | } 115 | 116 | $this->statement = $value; 117 | 118 | return $this; 119 | } 120 | public function getStatement() { return $this->statement; } 121 | 122 | public function setExtensions($value) { 123 | if (! $value instanceof Extensions) { 124 | $value = new Extensions($value); 125 | } 126 | 127 | $this->extensions = $value; 128 | 129 | return $this; 130 | } 131 | public function getExtensions() { return $this->extensions; } 132 | } 133 | -------------------------------------------------------------------------------- /src/ContextActivities.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 34 | } 35 | } 36 | 37 | private function _listSetter($prop, $value) { 38 | if (is_array($value)) { 39 | if (isset($value['id'])) { 40 | array_push($this->$prop, new Activity($value)); 41 | } 42 | else { 43 | foreach ($value as $k => $v) { 44 | if (! $value[$k] instanceof Activity) { 45 | $value[$k] = new Activity($value[$k]); 46 | } 47 | } 48 | $this->$prop = $value; 49 | } 50 | } 51 | elseif ($value instanceof Activity) { 52 | array_push($this->$prop, $value); 53 | } 54 | else { 55 | throw new \InvalidArgumentException('type of arg1 must be Activity, array of Activity properties, or array of Activity/array of Activity properties'); 56 | } 57 | return $this; 58 | } 59 | 60 | public function setCategory($value) { return $this->_listSetter('category', $value); } 61 | public function getCategory() { return $this->category; } 62 | public function setParent($value) { return $this->_listSetter('parent', $value); } 63 | public function getParent() { return $this->parent; } 64 | public function setGrouping($value) { return $this->_listSetter('grouping', $value); } 65 | public function getGrouping() { return $this->grouping; } 66 | public function setOther($value) { return $this->_listSetter('other', $value); } 67 | public function getOther() { return $this->other; } 68 | } 69 | -------------------------------------------------------------------------------- /src/Document.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 36 | } 37 | } 38 | 39 | public function setId($value) { $this->id = $value; return $this; } 40 | public function getId() { return $this->id; } 41 | public function setContentType($value) { $this->contentType = $value; return $this; } 42 | public function getContentType() { return $this->contentType; } 43 | public function setContent($value) { $this->content = $value; return $this; } 44 | public function getContent() { return $this->content; } 45 | public function setEtag($value) { $this->etag = $value; return $this; } 46 | public function getEtag() { return $this->etag; } 47 | 48 | public function setTimestamp($value) { 49 | if (isset($value)) { 50 | if ($value instanceof \DateTime) { 51 | // Use format('c') instead of format(\DateTime::ISO8601) due to bug in format(\DateTime::ISO8601) that generates an invalid timestamp. 52 | $value = $value->format('c'); 53 | } 54 | elseif (is_string($value)) { 55 | $value = $value; 56 | } 57 | else { 58 | throw new \InvalidArgumentException('type of arg1 must be string or DateTime'); 59 | } 60 | } 61 | 62 | $this->timestamp = $value; 63 | 64 | return $this; 65 | } 66 | public function getTimestamp() { return $this->timestamp; } 67 | } 68 | -------------------------------------------------------------------------------- /src/Extensions.php: -------------------------------------------------------------------------------- 1 | _map; 24 | 25 | $keys = array_unique( 26 | array_merge( 27 | isset($this->_map) ? array_keys($this->_map) : array(), 28 | isset($sigMap) ? array_keys($sigMap) : array() 29 | ) 30 | ); 31 | 32 | foreach ($keys as $key) { 33 | if (! isset($sigMap[$key])) { 34 | return array('success' => false, 'reason' => "$key not in signature"); 35 | } 36 | if (! isset($this->_map[$key])) { 37 | return array('success' => false, 'reason' => "$key not in this"); 38 | } 39 | if ($this->_map[$key] != $sigMap[$key]) { 40 | return array('success' => false, 'reason' => "$key does not match"); 41 | } 42 | } 43 | 44 | return array('success' => true, 'reason' => null); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/FromJSONTrait.php: -------------------------------------------------------------------------------- 1 | member)) { 30 | $this->setMember(array()); 31 | } 32 | } 33 | 34 | public function asVersion($version) { 35 | $result = parent::asVersion($version); 36 | 37 | if (count($this->member) > 0) { 38 | $result['member'] = array(); 39 | 40 | foreach ($this->member as $v) { 41 | array_push($result['member'], $v->asVersion($version)); 42 | } 43 | } 44 | 45 | return $result; 46 | } 47 | 48 | public function compareWithSignature($fromSig) { 49 | // 50 | // if this group is identified then it is the comparison 51 | // of the identifier that matters 52 | // 53 | if ($this->isIdentified() || $fromSig->isIdentified()) { 54 | return parent::compareWithSignature($fromSig); 55 | } 56 | 57 | // 58 | // anonymous groups get their member list compared, 59 | // short circuit when they don't have the same length 60 | // 61 | if (count($this->member) !== count($fromSig->member)) { 62 | return array('success' => false, 'reason' => 'Comparison of member list failed: array lengths differ'); 63 | } 64 | 65 | for ($i = 0; $i < count($this->member); $i++) { 66 | $comparison = $this->member[$i]->compareWithSignature($fromSig->member[$i]); 67 | if (! $comparison['success']) { 68 | return array('success' => false, 'reason' => "Comparison of member $i failed: " . $comparison['reason']); 69 | } 70 | } 71 | 72 | return array('success' => true, 'reason' => null); 73 | } 74 | 75 | public function setMember($value) { 76 | foreach ($value as $k => $v) { 77 | if (! $v instanceof Agent) { 78 | $value[$k] = new Agent($v); 79 | } 80 | } 81 | 82 | $this->member = $value; 83 | 84 | return $this; 85 | } 86 | public function getMember() { return $this->member; } 87 | public function addMember($value) { 88 | if (! $value instanceof Agent) { 89 | $value = new Agent($value); 90 | } 91 | 92 | array_push($this->member, $value); 93 | 94 | return $this; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/JSONParseErrorException.php: -------------------------------------------------------------------------------- 1 | malformedValue = $malformedValue; 30 | $this->jsonErrorNumber = (int) $jsonErrorNumber; 31 | $this->jsonErrorMessage = $jsonErrorMessage; 32 | 33 | $message = sprintf(self::$format, $malformedValue, $jsonErrorMessage, $jsonErrorNumber); 34 | 35 | parent::__construct($message, $jsonErrorNumber, $previous); 36 | } 37 | 38 | public function malformedValue() { 39 | return $this->malformedValue; 40 | } 41 | 42 | public function jsonErrorNumber() { 43 | return $this->jsonErrorNumber; 44 | } 45 | 46 | public function jsonErrorMessage() { 47 | return $this->jsonErrorMessage; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/LRSInterface.php: -------------------------------------------------------------------------------- 1 | success = (bool) $success; 30 | $this->content = $content; 31 | $this->httpResponse = $httpResponse; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LanguageMap.php: -------------------------------------------------------------------------------- 1 | _map); 33 | $preferredLanguage = $negotiator->getBest($acceptLanguage, $availableLanguages); 34 | 35 | $key = $availableLanguages[0]; 36 | if (isset($preferredLanguage)) { 37 | $key = $preferredLanguage->getValue(); 38 | } 39 | elseif (isset($this->_map['und'])) { 40 | $key = 'und'; 41 | } 42 | 43 | return $this->_map[$key]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Map.php: -------------------------------------------------------------------------------- 1 | _map = func_get_arg(0); 29 | } 30 | else { 31 | $this->_map = array(); 32 | } 33 | } 34 | 35 | public function asVersion($version = null) { 36 | if (! $this->isEmpty()) { 37 | return $this->_map; 38 | } 39 | } 40 | 41 | public function set($code, $value) { 42 | $this->_map[$code] = $value; 43 | } 44 | private function _unset($code) { 45 | unset($this->_map[$code]); 46 | } 47 | 48 | public function isEmpty() { 49 | return count($this->_map) === 0; 50 | } 51 | 52 | public function __call($func, $args) { 53 | switch ($func) { 54 | case 'unset': 55 | return $this->_unset($args[0]); 56 | break; 57 | default: 58 | throw new \BadMethodCallException(get_class($this) . "::$func() does not exist"); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Person.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 36 | } 37 | } 38 | 39 | public function asVersion($version) { 40 | $result = array( 41 | 'objectType' => $this->objectType 42 | ); 43 | if (isset($this->name)) { 44 | $result['name'] = $this->name; 45 | } 46 | if (isset($this->account)) { 47 | $result['account'] = array(); 48 | foreach ($this->account as $account) { 49 | if (! $account instanceof AgentAccount && is_array($account)) { 50 | $account = new AgentAccount($account); 51 | } 52 | array_push($result['account'], $account->asVersion($version)); 53 | } 54 | } 55 | if (isset($this->mbox_sha1sum)) { 56 | $result['mbox_sha1sum'] = $this->mbox_sha1sum; 57 | } 58 | if (isset($this->mbox)) { 59 | $result['mbox'] = $this->mbox; 60 | } 61 | if (isset($this->openid)) { 62 | $result['openid'] = $this->openid; 63 | } 64 | 65 | return $result; 66 | } 67 | 68 | public function getObjectType() { return $this->objectType; } 69 | 70 | public function setName($value) { $this->name = $value; return $this; } 71 | public function getName() { return $this->name; } 72 | 73 | public function setMbox($value) { $this->mbox = $value; return $this; } 74 | public function getMbox() { return $this->mbox; } 75 | 76 | public function setMbox_sha1sum($value) { $this->mbox_sha1sum = $value; return $this; } 77 | public function getMbox_sha1sum() {return $this->mbox_sha1sum;} 78 | 79 | public function setOpenid($value) { $this->openid = $value; return $this; } 80 | public function getOpenid() { return $this->openid; } 81 | 82 | public function setAccount($value) { $this->account = $value; return $this; } 83 | public function getAccount() { return $this->account; } 84 | } 85 | -------------------------------------------------------------------------------- /src/Result.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 36 | } 37 | 38 | if (! isset($this->extensions)) { 39 | $this->setExtensions(array()); 40 | } 41 | } 42 | 43 | private function _asVersion(&$result, $version) { 44 | // 45 | // empty string is an invalid duration 46 | // 47 | if (isset($result['duration']) && $result['duration'] == '') { 48 | unset($result['duration']); 49 | } 50 | } 51 | 52 | public function setScore($value) { 53 | if (! $value instanceof Score && is_array($value)) { 54 | $value = new Score($value); 55 | } 56 | 57 | $this->score = $value; 58 | 59 | return $this; 60 | } 61 | public function getScore() { return $this->score; } 62 | 63 | public function setSuccess($value) { $this->success = (bool) $value; return $this; } 64 | public function getSuccess() { return $this->success; } 65 | public function setCompletion($value) { $this->completion = (bool) $value; return $this; } 66 | public function getCompletion() { return $this->completion; } 67 | public function setDuration($value) { $this->duration = $value; return $this; } 68 | public function getDuration() { return $this->duration; } 69 | public function setResponse($value) { $this->response = $value; return $this; } 70 | public function getResponse() { return $this->response; } 71 | 72 | public function setExtensions($value) { 73 | if (! $value instanceof Extensions) { 74 | $value = new Extensions($value); 75 | } 76 | 77 | $this->extensions = $value; 78 | 79 | return $this; 80 | } 81 | public function getExtensions() { return $this->extensions; } 82 | } 83 | -------------------------------------------------------------------------------- /src/Score.php: -------------------------------------------------------------------------------- 1 | $aRawValue, 80 | 'min' => $aMin, 81 | 'max' => $aMax, 82 | 'scaled' => $aScaledValue 83 | ]; 84 | } 85 | $this->_fromArray($aRawValue); 86 | } 87 | 88 | /** 89 | * @param float $value 90 | * @throws InvalidArgumentException 91 | * @return self 92 | */ 93 | public function setScaled($value) { 94 | if ($value < static::SCALE_MIN) { 95 | throw new InvalidArgumentException( 96 | sprintf( "Value must be greater than or equal to %s [%s]", static::SCALE_MIN, $value) 97 | ); 98 | } 99 | if ($value > static::SCALE_MAX) { 100 | throw new InvalidArgumentException( 101 | sprintf( "Value must be less than or equal to %s [%s]", static::SCALE_MAX, $value) 102 | ); 103 | } 104 | $this->scaled = (float) $value; 105 | return $this; 106 | } 107 | 108 | /** 109 | * @return null|float 110 | */ 111 | public function getScaled() { 112 | return $this->scaled; 113 | } 114 | 115 | /** 116 | * @param float $value 117 | * @throws InvalidArgumentException 118 | * @return self 119 | */ 120 | public function setRaw($value) { 121 | if (isset($this->min) && $value < $this->min) { 122 | throw new InvalidArgumentException( 123 | sprintf("Value must be greater than or equal to 'min' (%s) [%s]", $this->min, $value) 124 | ); 125 | } 126 | if (isset($this->max) && $value > $this->max) { 127 | throw new InvalidArgumentException( 128 | sprintf("Value must be less than or equal to 'max' (%s) [%s]", $this->max, $value) 129 | ); 130 | } 131 | 132 | $this->raw = (float) $value; 133 | return $this; 134 | } 135 | 136 | /** 137 | * @return null|float 138 | */ 139 | public function getRaw() { 140 | return $this->raw; 141 | } 142 | 143 | /** 144 | * @param float $value 145 | * @throws InvalidArgumentException 146 | * @return self 147 | */ 148 | public function setMin($value) { 149 | if (isset($this->raw) && $value > $this->raw) { 150 | throw new InvalidArgumentException( 151 | sprintf("Value must be less than or equal to 'raw' (%s) [%s]", $this->raw, $value) 152 | ); 153 | } 154 | if (isset($this->max) && $value >= $this->max) { 155 | throw new InvalidArgumentException( 156 | sprintf("Value must be less than 'max' (%s) [%s]", $this->max, $value) 157 | ); 158 | } 159 | $this->min = (float) $value; 160 | return $this; 161 | } 162 | 163 | /** 164 | * @return null|float 165 | */ 166 | public function getMin() { 167 | return $this->min; 168 | } 169 | 170 | /** 171 | * @param float $value 172 | * @throws InvalidArgumentException 173 | * @return self 174 | */ 175 | public function setMax($value) { 176 | if (isset($this->raw) && $value < $this->raw) { 177 | throw new InvalidArgumentException( 178 | sprintf("Value must be greater than or equal to 'raw' (%s) [%s]", $this->raw, $value) 179 | ); 180 | } 181 | if (isset($this->min) && $value <= $this->min) { 182 | throw new InvalidArgumentException( 183 | sprintf("Value must be greater than 'min' (%s) [%s]", $this->min, $value) 184 | ); 185 | } 186 | $this->max = (float) $value; 187 | return $this; 188 | } 189 | 190 | /** 191 | * @return null|float 192 | */ 193 | public function getMax() { 194 | return $this->max; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/SignatureComparisonTrait.php: -------------------------------------------------------------------------------- 1 | $value) { 36 | // 37 | // skip properties that start with an underscore to allow 38 | // storing information that isn't included in statement 39 | // structure etc. (see Attachment.content for example) 40 | // 41 | // also allow a class to specify a list of additional 42 | // properties that should not be included in verification 43 | // 44 | if (strpos($property, '_') === 0 || $property === 'objectType' || in_array($property, $skip)) { 45 | continue; 46 | } 47 | 48 | $result = self::doMatch($value, $fromSig->$property, $property); 49 | if (! $result['success']) { 50 | return $result; 51 | } 52 | } 53 | 54 | return array( 55 | 'success' => true, 56 | 'reason' => null 57 | ); 58 | } 59 | 60 | private static function doMatch($a, $b, $description) { 61 | $result = array( 62 | 'success' => false, 63 | 'reason' => null 64 | ); 65 | if ((isset($a) && ! isset($b)) || (isset($b) && ! isset($a))) { 66 | $result['reason'] = "Comparison of $description failed: value not present in this or signature"; 67 | return $result; 68 | } 69 | 70 | if (is_object($a) && ! ($b instanceof $a)) { 71 | $result['reason'] = "Comparison of $description failed: not a " . get_class($a) . " value"; 72 | return $result; 73 | } 74 | 75 | if ($a instanceof ComparableInterface) { 76 | $comparison = $a->compareWithSignature($b); 77 | if (! $comparison['success']) { 78 | $result['reason'] = "Comparison of $description failed: " . $comparison['reason']; 79 | return $result; 80 | } 81 | } 82 | else { 83 | if (is_array($a)) { 84 | if (! is_array($b)) { 85 | $result['reason'] = "Comparison of $description failed: not an array in signature"; 86 | return $result; 87 | } 88 | 89 | if (count($a) !== count($b)) { 90 | $result['reason'] = "Comparison of $description failed: array lengths differ"; 91 | return $result; 92 | } 93 | 94 | for ($i = 0; $i < count($a); $i++) { 95 | $comparison = self::doMatch($a[$i], $b[$i], $description . "[$i]"); 96 | if (! $comparison['success']) { 97 | return $comparison; 98 | } 99 | } 100 | } 101 | else { 102 | if ($a != $b) { 103 | $result['reason'] = "Comparison of $description failed: value is not the same"; 104 | return $result; 105 | } 106 | } 107 | } 108 | 109 | $result['success'] = true; 110 | return $result; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/State.php: -------------------------------------------------------------------------------- 1 | activity = $value; 32 | 33 | return $this; 34 | } 35 | public function getActivity() { return $this->activity; } 36 | 37 | public function setAgent($value) { 38 | if ((! $value instanceof Agent && ! $value instanceof Group) && is_array($value)) { 39 | if (isset($value['objectType']) && $value['objectType'] === 'Group') { 40 | $value = new Group($value); 41 | } 42 | else { 43 | $value = new Agent($value); 44 | } 45 | } 46 | 47 | $this->agent = $value; 48 | 49 | return $this; 50 | } 51 | public function getAgent() { return $this->agent; } 52 | 53 | public function setRegistration($value) { 54 | if (isset($value) && ! preg_match(Util::UUID_REGEX, $value)) { 55 | throw new \InvalidArgumentException('arg1 must be a UUID'); 56 | } 57 | 58 | $this->registration = $value; 59 | 60 | return $this; 61 | } 62 | public function getRegistration() { return $this->registration; } 63 | } 64 | -------------------------------------------------------------------------------- /src/StatementBase.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 49 | 50 | // 51 | // 'object' isn't in the list of properties so ._fromArray doesn't 52 | // pick it up correctly, but 'target' and 'object' shouldn't be in 53 | // the args at the same time, so handle 'object' here 54 | // 55 | if (isset($arg['object'])) { 56 | $this->setObject($arg['object']); 57 | } 58 | } 59 | } 60 | 61 | private function _asVersion(&$result, $version) { 62 | if (isset($result['target'])) { 63 | $result['object'] = $result['target']; 64 | unset($result['target']); 65 | } 66 | } 67 | 68 | public function compareWithSignature($fromSig) { 69 | foreach (array('actor', 'verb', 'target', 'context', 'result') as $property) { 70 | if (! isset($this->$property) && ! isset($fromSig->$property)) { 71 | continue; 72 | } 73 | if (isset($this->$property) && ! isset($fromSig->$property)) { 74 | return array('success' => false, 'reason' => "Comparison of $property failed: value not in signature"); 75 | } 76 | if (isset($fromSig->$property) && ! isset($this->$property)) { 77 | return array('success' => false, 'reason' => "Comparison of $property failed: value not in this"); 78 | } 79 | 80 | $result = $this->$property->compareWithSignature($fromSig->$property); 81 | if (! $result['success']) { 82 | return array('success' => false, 'reason' => "Comparison of $property failed: " . $result['reason']); 83 | } 84 | } 85 | 86 | if (isset($this->timestamp) || isset($fromSig->timestamp)) { 87 | if (isset($this->timestamp) && ! isset($fromSig->timestamp)) { 88 | return array('success' => false, 'reason' => 'Comparison of timestamp failed: value not in signature'); 89 | } 90 | if (isset($fromSig->timestamp) && ! isset($this->timestamp)) { 91 | return array('success' => false, 'reason' => 'Comparison of timestamp failed: value not in this'); 92 | } 93 | 94 | $a = new \DateTime ($this->timestamp); 95 | $b = new \DateTime ($fromSig->timestamp); 96 | 97 | if ($a != $b) { 98 | return array('success' => false, 'reason' => 'Comparison of timestamp failed: value is not the same'); 99 | } 100 | 101 | // 102 | // DateTime's diff doesn't take into account subsecond precision 103 | // even though it can store it, so manually check that 104 | // 105 | if ($a->format('u') !== $b->format('u')) { 106 | return array('success' => false, 'reason' => 'Comparison of timestamp failed: value is not the same'); 107 | } 108 | } 109 | 110 | return array('success' => true, 'reason' => null); 111 | } 112 | 113 | public function setActor($value) { 114 | if ((! $value instanceof Agent && ! $value instanceof Group) && is_array($value)) { 115 | if (isset($value['objectType']) && $value['objectType'] === 'Group') { 116 | $value = new Group($value); 117 | } 118 | else { 119 | $value = new Agent($value); 120 | } 121 | } 122 | 123 | $this->actor = $value; 124 | 125 | return $this; 126 | } 127 | public function getActor() { return $this->actor; } 128 | 129 | public function setVerb($value) { 130 | if (! $value instanceof Verb) { 131 | $value = new Verb($value); 132 | } 133 | 134 | $this->verb = $value; 135 | 136 | return $this; 137 | } 138 | public function getVerb() { return $this->verb; } 139 | 140 | public function setTarget($value) { 141 | if (! $value instanceof StatementTargetInterface && is_array($value)) { 142 | if (isset($value['objectType'])) { 143 | if ($value['objectType'] === 'Activity') { 144 | $value = new Activity($value); 145 | } 146 | elseif ($value['objectType'] === 'Agent') { 147 | $value = new Agent($value); 148 | } 149 | elseif ($value['objectType'] === 'Group') { 150 | $value = new Group($value); 151 | } 152 | elseif ($value['objectType'] === 'StatementRef') { 153 | $value = new StatementRef($value); 154 | } 155 | elseif ($value['objectType'] === 'SubStatement') { 156 | $value = new SubStatement($value); 157 | } 158 | else { 159 | throw new \InvalidArgumentException('arg1 must implement the StatementTargetInterface objectType not recognized:' . $value['objectType']); 160 | } 161 | } 162 | else { 163 | $value = new Activity($value); 164 | } 165 | } 166 | 167 | $this->target = $value; 168 | 169 | return $this; 170 | } 171 | public function getTarget() { return $this->target; } 172 | 173 | // sugar methods 174 | public function setObject($value) { return $this->setTarget($value); } 175 | public function getObject() { return $this->getTarget(); } 176 | 177 | public function setResult($value) { 178 | if (! $value instanceof Result && is_array($value)) { 179 | $value = new Result($value); 180 | } 181 | 182 | $this->result = $value; 183 | 184 | return $this; 185 | } 186 | public function getResult() { return $this->result; } 187 | 188 | public function setContext($value) { 189 | if (! $value instanceof Context && is_array($value)) { 190 | $value = new Context($value); 191 | } 192 | 193 | $this->context = $value; 194 | 195 | return $this; 196 | } 197 | public function getContext() { return $this->context; } 198 | 199 | public function setTimestamp($value) { 200 | if (isset($value)) { 201 | if ($value instanceof \DateTime) { 202 | // Use format('c') instead of format(\DateTime::ISO8601) due to bug in format(\DateTime::ISO8601) that generates an invalid timestamp. 203 | $value = $value->format('c'); 204 | } 205 | elseif (is_string($value)) { 206 | $value = $value; 207 | } 208 | else { 209 | throw new \InvalidArgumentException('type of arg1 must be string or DateTime'); 210 | } 211 | } 212 | 213 | $this->timestamp = $value; 214 | 215 | return $this; 216 | } 217 | public function getTimestamp() { return $this->timestamp; } 218 | } 219 | -------------------------------------------------------------------------------- /src/StatementRef.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 35 | } 36 | } 37 | 38 | public function getObjectType() { return $this->objectType; } 39 | 40 | public function setId($value) { 41 | if (isset($value) && ! preg_match(Util::UUID_REGEX, $value)) { 42 | throw new InvalidArgumentException('arg1 must be a UUID'); 43 | } 44 | $this->id = $value; 45 | return $this; 46 | } 47 | public function getId() { return $this->id; } 48 | } 49 | -------------------------------------------------------------------------------- /src/StatementTargetInterface.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 32 | } 33 | } 34 | 35 | public function setStatements($value) { 36 | foreach ($value as $k => $v) { 37 | if (! $value[$k] instanceof Statement) { 38 | $value[$k] = new Statement($v); 39 | } 40 | } 41 | 42 | $this->statements = $value; 43 | 44 | return $this; 45 | } 46 | public function getStatements() { return $this->statements; } 47 | public function setMore($value) { $this->more = $value; return $this; } 48 | public function getMore() { return $this->more; } 49 | } 50 | -------------------------------------------------------------------------------- /src/SubStatement.php: -------------------------------------------------------------------------------- 1 | objectType; } 25 | } 26 | -------------------------------------------------------------------------------- /src/Util.php: -------------------------------------------------------------------------------- 1 | > 4; 44 | $time_hi_and_version = $time_hi_and_version | 0x4000; 45 | 46 | /** 47 | * Set the two most significant bits (bits 6 and 7) of the 48 | * clock_seq_hi_and_reserved to zero and one, respectively. 49 | */ 50 | $clock_seq_hi_and_reserved = hexdec($clock_seq_hi_and_reserved); 51 | $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved >> 2; 52 | $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved | 0x8000; 53 | 54 | return sprintf( 55 | '%08s-%04s-%04x-%04x-%012s', 56 | $time_low, 57 | $time_mid, 58 | $time_hi_and_version, 59 | $clock_seq_hi_and_reserved, 60 | $node 61 | ); 62 | } 63 | 64 | // 65 | // Returns the current date+time in string format with 66 | // sub-second precision 67 | // 68 | // Based on code from 69 | // http://stackoverflow.com/a/4414060/1464957 70 | public static function getTimestamp() { 71 | $time = microtime(true); 72 | $microseconds = sprintf('%06d', ($time - floor($time)) * 1000000); 73 | $millseconds = round($microseconds, -3)/1000; 74 | $millsecondsStr = str_pad($millseconds, 3, '0', STR_PAD_LEFT); 75 | $date = (new \DateTime(null, new \DateTimeZone("UTC")))->format('c'); 76 | 77 | $position = strrpos($date, '+'); 78 | $date = substr($date,0,$position).'.'.$millsecondsStr.substr($date,$position); 79 | 80 | return $date; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Verb.php: -------------------------------------------------------------------------------- 1 | _fromArray($arg); 34 | } 35 | 36 | if (! isset($this->display)) { 37 | $this->setDisplay(array()); 38 | } 39 | } 40 | 41 | // FEATURE: check IRI? 42 | public function setId($value) { $this->id = $value; return $this; } 43 | public function getId() { return $this->id; } 44 | 45 | public function setDisplay($value) { 46 | if (! $value instanceof LanguageMap) { 47 | $value = new LanguageMap($value); 48 | } 49 | 50 | $this->display = $value; 51 | 52 | return $this; 53 | } 54 | public function getDisplay() { return $this->display; } 55 | 56 | static public function Voided() { 57 | return new self( 58 | [ 59 | 'id' => 'http://adlnet.gov/expapi/verbs/voided', 60 | 'display' => [ 61 | 'en-US' => 'voided' 62 | ] 63 | ] 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Version.php: -------------------------------------------------------------------------------- 1 | bool */ 43 | private static $supported = [ 44 | self::V103 => true, 45 | self::V102 => true, 46 | self::V101 => true, 47 | self::V100 => true, 48 | self::V095 => false 49 | ]; 50 | 51 | /** @var self[] */ 52 | private static $instances = []; 53 | 54 | /** @var string */ 55 | private $value; 56 | 57 | /** 58 | * Constructor 59 | * 60 | * @param string $aValue a version value 61 | * @throws InvalidArgumentException when the value is not recognized 62 | */ 63 | private function __construct($aValue) { 64 | if (!isset(static::$supported[$aValue])) { 65 | throw new InvalidArgumentException("Invalid version [$aValue]"); 66 | } 67 | $this->value = $aValue; 68 | } 69 | 70 | /** 71 | * Does the value match? 72 | * 73 | * @param string $aValue a value to check 74 | * @return bool 75 | */ 76 | public function hasValue($aValue) { 77 | return $this->value === (string) $aValue; 78 | } 79 | 80 | /** 81 | * Is the value contained in a list of versions? 82 | * 83 | * @param string[] $aValueList a list of values to check 84 | * @return bool 85 | */ 86 | public function hasAnyValue(array $aValueList) { 87 | return in_array($this->value, $aValueList); 88 | } 89 | 90 | /** 91 | * Is this the latest version? 92 | * 93 | * @return bool 94 | */ 95 | public function isLatest() { 96 | return $this->value === static::latest(); 97 | } 98 | 99 | /** 100 | * Is this a supported version? 101 | * 102 | * @return bool 103 | */ 104 | public function isSupported() { 105 | return static::$supported[$this->value]; 106 | } 107 | 108 | /** 109 | * Convert the object to a string 110 | * 111 | * @return string 112 | */ 113 | public function __toString() { 114 | return $this->value; 115 | } 116 | 117 | /** 118 | * Factory constructor 119 | * 120 | * @example $version = Version::V101(); 121 | * @param string $aValue the called method as a version value 122 | * @param array $arguments unused arguments passed to the method 123 | * @return self 124 | */ 125 | public static function __callStatic($aValue, array $arguments = []) { 126 | $aValue = trim(preg_replace("#v(\d)(95|\d)(\d)?#i", '$1.$2.$3', $aValue), "."); 127 | if (!isset(static::$instances[$aValue])) { 128 | static::$instances[$aValue] = new static($aValue); 129 | } 130 | return static::$instances[$aValue]; 131 | } 132 | 133 | /** 134 | * Convert a string into a Version instance 135 | * 136 | * @param string $aValue a version value 137 | * @return self 138 | */ 139 | public static function fromString($aValue) { 140 | $aValue = str_replace(".", "", $aValue); 141 | return static::{"V$aValue"}(); 142 | } 143 | 144 | /** 145 | * List all supported versions 146 | * 147 | * @return string[] 148 | */ 149 | public static function supported() { 150 | return array_keys(array_filter(static::$supported, function($supported) { 151 | return $supported === true; 152 | })); 153 | } 154 | 155 | /** 156 | * Retrieve the most recent version 157 | * 158 | * @return string 159 | */ 160 | public static function latest() { 161 | return array_keys(static::$supported)[0]; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/VersionableInterface.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\About', $obj); 28 | $this->assertAttributeEmpty('version', $obj, 'version empty'); 29 | $this->assertAttributeNotEmpty('extensions', $obj, 'extenstions not empty'); 30 | } 31 | 32 | public function testUsesArraySetterTrait() { 33 | $this->assertContains('TinCan\ArraySetterTrait', class_uses('TinCan\About')); 34 | } 35 | 36 | public function testUsesFromJSONTrait() { 37 | $this->assertContains('TinCan\FromJSONTrait', class_uses('TinCan\About')); 38 | } 39 | 40 | public function testUsesAsVersionTrait() { 41 | $this->assertContains('TinCan\AsVersionTrait', class_uses('TinCan\About')); 42 | } 43 | 44 | public function testVersion() { 45 | $obj = new About(); 46 | $version = [self::VERSION_1]; 47 | $this->assertSame($obj, $obj->setVersion($version)); 48 | $this->assertSame($version, $obj->getVersion()); 49 | } 50 | 51 | public function testExtensionsWithArray() { 52 | $obj = new About(); 53 | $this->assertSame($obj, $obj->setExtensions(['foo' => 'bar'])); 54 | $this->assertInstanceOf('TinCan\Extensions', $obj->getExtensions()); 55 | $this->assertFalse($obj->getExtensions()->isEmpty()); 56 | } 57 | 58 | // TODO: need to loop versions 59 | public function testAsVersion() { 60 | $args = ['version' => [self::VERSION_1]]; 61 | $obj = new About($args); 62 | $versioned = $obj->asVersion(self::VERSION_1); 63 | 64 | $this->assertEquals($versioned, $args, "version only: 1.0.0"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/ActivityDefinitionTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\ActivityDefinition', $obj); 46 | foreach ($this->emptyProperties as $property) { 47 | $this->assertAttributeEmpty($property, $obj, "$property empty"); 48 | } 49 | foreach ($this->nonEmptyProperties as $property) { 50 | $this->assertAttributeNotEmpty($property, $obj, "$property not empty"); 51 | } 52 | } 53 | 54 | public function testUsesArraySetterTrait() { 55 | $this->assertContains('TinCan\ArraySetterTrait', class_uses('TinCan\ActivityDefinition')); 56 | } 57 | 58 | public function testUsesFromJSONTrait() { 59 | $this->assertContains('TinCan\FromJSONTrait', class_uses('TinCan\ActivityDefinition')); 60 | } 61 | 62 | public function testUsesAsVersionTrait() { 63 | $this->assertContains('TinCan\AsVersionTrait', class_uses('TinCan\ActivityDefinition')); 64 | } 65 | 66 | // TODO: need more robust test (happy-path) 67 | public function testAsVersion() { 68 | $args = [ 69 | 'name' => ['en' => self::NAME], 70 | 'extensions' => [] 71 | ]; 72 | $args['extensions'][COMMON_EXTENSION_ID_1] = 'test'; 73 | 74 | $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 75 | $versioned = $obj->asVersion('1.0.0'); 76 | 77 | $this->assertEquals($versioned, $args, "serialized version matches original"); 78 | } 79 | 80 | public function testAsVersionEmpty() { 81 | $args = []; 82 | 83 | $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 84 | $versioned = $obj->asVersion('1.0.0'); 85 | 86 | $this->assertEquals($versioned, $args, "serialized version matches original"); 87 | } 88 | 89 | public function testAsVersionEmptyLanguageMap() { 90 | $args = ['name' => []]; 91 | 92 | $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 93 | $versioned = $obj->asVersion('1.0.0'); 94 | 95 | unset($args['name']); 96 | 97 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 98 | } 99 | 100 | public function testAsVersionEmptyExtensions() { 101 | $args = ['extensions' => []]; 102 | 103 | $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 104 | $versioned = $obj->asVersion('1.0.0'); 105 | 106 | unset($args['extensions']); 107 | 108 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 109 | } 110 | 111 | public function testAsVersionEmptyStringInLanguageMap() { 112 | $args = ['name' => ['en' => '']]; 113 | 114 | $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 115 | $versioned = $obj->asVersion('1.0.0'); 116 | 117 | $this->assertEquals($versioned, $args, "serialized version matches original"); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tests/ActivityProfileTest.php: -------------------------------------------------------------------------------- 1 | setActivity(['id' => COMMON_ACTIVITY_ID]); 27 | 28 | $this->assertInstanceOf('TinCan\Activity', $profile->getActivity()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/ActivityTest.php: -------------------------------------------------------------------------------- 1 | 'http://id.tincanapi.com/activitytype/unit-test', 30 | 'name' => [ 31 | 'en-US' => 'test', 32 | 'en-GB' => 'test', 33 | 'es' => 'prueba' 34 | ] 35 | ]; 36 | } 37 | 38 | public function testInstantiation() { 39 | $obj = new Activity(); 40 | $this->assertInstanceOf('TinCan\Activity', $obj); 41 | $this->assertAttributeEmpty('id', $obj, 'id empty'); 42 | $this->assertAttributeEmpty('definition', $obj, 'definition empty'); 43 | } 44 | 45 | public function testFromJSONInvalidNull() { 46 | $this->setExpectedException('TinCan\JSONParseErrorException'); 47 | $obj = Activity::fromJSON(null); 48 | } 49 | 50 | public function testFromJSONInvalidEmptyString() { 51 | $this->setExpectedException('TinCan\JSONParseErrorException'); 52 | $obj = Activity::fromJSON(''); 53 | } 54 | 55 | public function testFromJSONInvalidMalformed() { 56 | $this->setExpectedException('TinCan\JSONParseErrorException'); 57 | $obj = Activity::fromJSON('{id:"some value"}'); 58 | } 59 | 60 | public function testFromJSONIDOnly() { 61 | $obj = Activity::fromJSON('{"id":"' . COMMON_ACTIVITY_ID . '"}'); 62 | $this->assertInstanceOf('TinCan\Activity', $obj); 63 | $this->assertAttributeEquals(COMMON_ACTIVITY_ID, 'id', $obj, 'id matches'); 64 | $this->assertAttributeEmpty('definition', $obj, 'definition empty'); 65 | } 66 | 67 | // TODO: need to loop versions 68 | public function testAsVersion() { 69 | $args = [ 70 | 'id' => COMMON_ACTIVITY_ID, 71 | 'definition' => [ 72 | 'name' => [ 73 | 'en' => 'test' 74 | ] 75 | ] 76 | ]; 77 | 78 | $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 79 | $versioned = $obj->asVersion('1.0.0'); 80 | 81 | $args['objectType'] = 'Activity'; 82 | 83 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 84 | } 85 | 86 | public function testAsVersionEmpty() { 87 | $args = []; 88 | 89 | $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 90 | $versioned = $obj->asVersion('1.0.0'); 91 | 92 | $args['objectType'] = 'Activity'; 93 | 94 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 95 | } 96 | 97 | public function testAsVersionIdOnly() { 98 | $args = [ 'id' => COMMON_ACTIVITY_ID ]; 99 | 100 | $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 101 | $versioned = $obj->asVersion('1.0.0'); 102 | 103 | $args['objectType'] = 'Activity'; 104 | 105 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 106 | } 107 | 108 | public function testAsVersionEmptyDefinition() { 109 | $args = [ 110 | 'id' => COMMON_ACTIVITY_ID, 111 | 'definition' => [] 112 | ]; 113 | 114 | $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 115 | $versioned = $obj->asVersion('1.0.0'); 116 | 117 | $args['objectType'] = 'Activity'; 118 | unset($args['definition']); 119 | 120 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 121 | } 122 | 123 | public function testCompareWithSignature() { 124 | $full = [ 125 | 'id' => COMMON_ACTIVITY_ID, 126 | 'definition' => self::$DEFINITION 127 | ]; 128 | $definition2 = array_replace(self::$DEFINITION, ['type' => 'http://id.tincanapi.com/activitytype/unit-test-suite']); 129 | $cases = [ 130 | [ 131 | 'description' => 'all null', 132 | 'objArgs' => [] 133 | ], 134 | [ 135 | 'description' => 'id', 136 | 'objArgs' => ['id' => COMMON_ACTIVITY_ID] 137 | ], 138 | [ 139 | 'description' => 'definition', 140 | 'objArgs' => ['definition' => self::$DEFINITION] 141 | ], 142 | [ 143 | 'description' => 'all', 144 | 'objArgs' => $full 145 | ], 146 | 147 | // 148 | // definitions are not matched for signature purposes because they 149 | // are not supposed to affect the meaning of the statement and may 150 | // be supplied in canonical format, etc. 151 | // 152 | [ 153 | 'description' => 'definition only: mismatch (allowed)', 154 | 'objArgs' => ['definition' => self::$DEFINITION ], 155 | 'sigArgs' => ['definition' => $definition2 ] 156 | ], 157 | [ 158 | 'description' => 'full: definition mismatch (allowed)', 159 | 'objArgs' => $full, 160 | 'sigArgs' => array_replace($full, ['definition' => $definition2 ]) 161 | ], 162 | 163 | [ 164 | 'description' => 'id only: mismatch', 165 | 'objArgs' => ['id' => COMMON_ACTIVITY_ID ], 166 | 'sigArgs' => ['id' => COMMON_ACTIVITY_ID . '/invalid' ], 167 | 'reason' => 'Comparison of id failed: value is not the same' 168 | ], 169 | [ 170 | 'description' => 'full: id mismatch', 171 | 'objArgs' => $full, 172 | 'sigArgs' => array_replace($full, ['id' => COMMON_ACTIVITY_ID . '/invalid']), 173 | 'reason' => 'Comparison of id failed: value is not the same' 174 | ] 175 | ]; 176 | $this->runSignatureCases("TinCan\Activity", $cases); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /tests/AgentAccountTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\AgentAccount', $obj); 31 | $this->assertAttributeEmpty('homePage', $obj, 'homePage empty'); 32 | $this->assertAttributeEmpty('name', $obj, 'name empty'); 33 | } 34 | 35 | public function testUsesArraySetterTrait() { 36 | $this->assertContains('TinCan\ArraySetterTrait', class_uses('TinCan\AgentAccount')); 37 | } 38 | 39 | public function testUsesFromJSONTrait() { 40 | $this->assertContains('TinCan\FromJSONTrait', class_uses('TinCan\AgentAccount')); 41 | } 42 | 43 | public function testUsesAsVersionTrait() { 44 | $this->assertContains('TinCan\AsVersionTrait', class_uses('TinCan\AgentAccount')); 45 | } 46 | 47 | public function testName() { 48 | $obj = new AgentAccount(); 49 | $name = COMMON_ACCT_NAME; 50 | $this->assertSame($obj, $obj->setName($name)); 51 | $this->assertSame($name, $obj->getName()); 52 | } 53 | 54 | public function testHomePage() { 55 | $obj = new AgentAccount(); 56 | $homePage = COMMON_ACCT_HOMEPAGE; 57 | $this->assertSame($obj, $obj->setHomePage($homePage)); 58 | $this->assertSame($homePage, $obj->getHomePage()); 59 | } 60 | 61 | public function testAsVersion() { 62 | $args = [ 63 | 'name' => COMMON_ACCT_NAME, 64 | 'homePage' => COMMON_ACCT_HOMEPAGE 65 | ]; 66 | 67 | $obj = AgentAccount::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 68 | $versioned = $obj->asVersion('1.0.0'); 69 | 70 | $this->assertEquals($versioned, $args, "serialized version matches original"); 71 | } 72 | 73 | public function testAsVersionEmpty() { 74 | $args = []; 75 | 76 | $obj = AgentAccount::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 77 | $versioned = $obj->asVersion('1.0.0'); 78 | 79 | $this->assertEquals($versioned, $args, "serialized version matches original"); 80 | } 81 | 82 | public function testAsVersionEmptyStrings() { 83 | $args = [ 84 | 'name' => '', 85 | 'homePage' => '' 86 | ]; 87 | 88 | $obj = AgentAccount::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 89 | $versioned = $obj->asVersion('1.0.0'); 90 | 91 | $this->assertEquals($versioned, $args, "serialized version matches original"); 92 | } 93 | 94 | public function testCompareWithSignature() { 95 | $full = [ 96 | 'homePage' => COMMON_ACCT_HOMEPAGE, 97 | 'name' => COMMON_ACCT_NAME 98 | ]; 99 | $cases = [ 100 | [ 101 | 'description' => 'all null', 102 | 'objArgs' => [] 103 | ], 104 | [ 105 | 'description' => 'homePage', 106 | 'objArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE] 107 | ], 108 | [ 109 | 'description' => 'name', 110 | 'objArgs' => ['name' => COMMON_ACCT_NAME] 111 | ], 112 | [ 113 | 'description' => 'all', 114 | 'objArgs' => $full 115 | ], 116 | [ 117 | 'description' => 'homepage only: mismatch', 118 | 'objArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE], 119 | 'sigArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE . '/invalid'], 120 | 'reason' => 'Comparison of homePage failed: value is not the same' 121 | ], 122 | [ 123 | 'description' => 'name only: mismatch', 124 | 'objArgs' => ['name' => COMMON_ACCT_NAME], 125 | 'sigArgs' => ['name' => 'diff'], 126 | 'reason' => 'Comparison of name failed: value is not the same' 127 | ], 128 | [ 129 | 'description' => 'full: homePage mismatch', 130 | 'objArgs' => $full, 131 | 'sigArgs' => array_replace($full, ['homePage' => COMMON_ACCT_HOMEPAGE . '/invalid']), 132 | 'reason' => 'Comparison of homePage failed: value is not the same' 133 | ], 134 | [ 135 | 'description' => 'full: name mismatch', 136 | 'objArgs' => $full, 137 | 'sigArgs' => array_replace($full, ['name' => 'diff']), 138 | 'reason' => 'Comparison of name failed: value is not the same' 139 | ], 140 | [ 141 | 'description' => 'full: both mismatch', 142 | 'objArgs' => $full, 143 | 'sigArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE . '/invalid', 'name' => 'diff'], 144 | 'reason' => 'Comparison of name failed: value is not the same' 145 | ] 146 | ]; 147 | $this->runSignatureCases("TinCan\AgentAccount", $cases); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tests/AgentProfileTest.php: -------------------------------------------------------------------------------- 1 | setAgent(['mbox' => COMMON_MBOX]); 28 | 29 | $this->assertInstanceOf('TinCan\Agent', $profile->getAgent()); 30 | 31 | $profile->setAgent(['objectType' => 'Group']); 32 | 33 | $this->assertInstanceOf('TinCan\Group', $profile->getAgent()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/AsVersionTraitTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(trait_exists('TinCan\AsVersionTrait')); 24 | } 25 | 26 | public function testAsVersionReturnsArray() { 27 | $trait = $this->getMockForTrait('TinCan\AsVersionTrait'); 28 | $this->assertInternalType('array', $trait->asVersion('test')); 29 | } 30 | 31 | public function testMagicSetThrowsException() { 32 | $this->setExpectedException('DomainException'); 33 | $trait = $this->getMockForTrait('TinCan\AsVersionTrait'); 34 | $trait->foo = 'bar'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/AttachmentTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\Attachment', $obj); 51 | foreach ($this->emptyProperties as $property) { 52 | $this->assertAttributeEmpty($property, $obj, "$property empty"); 53 | } 54 | foreach ($this->nonEmptyProperties as $property) { 55 | $this->assertAttributeNotEmpty($property, $obj, "$property not empty"); 56 | } 57 | } 58 | 59 | public function testUsesArraySetterTrait() { 60 | $this->assertContains('TinCan\ArraySetterTrait', class_uses('TinCan\Attachment')); 61 | } 62 | 63 | public function testUsesFromJSONTrait() { 64 | $this->assertContains('TinCan\FromJSONTrait', class_uses('TinCan\Attachment')); 65 | } 66 | 67 | public function testUsesAsVersionTrait() { 68 | $this->assertContains('TinCan\AsVersionTrait', class_uses('TinCan\Attachment')); 69 | } 70 | 71 | public function testContent() { 72 | $obj = new Attachment(); 73 | $obj->setContent(self::CONTENT_STR); 74 | 75 | $this->assertSame($obj->getContent(), self::CONTENT_STR, 'content body'); 76 | $this->assertSame($obj->getLength(), self::CONTENT_LENGTH, 'length'); 77 | $this->assertSame($obj->getSha2(), self::CONTENT_SHA2, 'sha2'); 78 | } 79 | 80 | public function testHasContent() { 81 | $no_content = new Attachment(); 82 | $this->assertFalse($no_content->hasContent()); 83 | 84 | $has_content = new Attachment( 85 | [ 86 | 'content' => self::CONTENT_STR 87 | ] 88 | ); 89 | $this->assertTrue($has_content->hasContent()); 90 | 91 | $set_content = new Attachment(); 92 | $set_content->setContent(self::CONTENT_STR); 93 | $this->assertTrue($set_content->hasContent()); 94 | } 95 | 96 | // TODO: need more robust test (happy-path) 97 | public function testAsVersion() { 98 | $args = [ 99 | 'usageType' => self::USAGE_TYPE, 100 | 'display' => ['en-US' => self::DISPLAY], 101 | 'description' => ['en-US' => self::DESCRIPTION], 102 | 'contentType' => self::CONTENT_TYPE, 103 | 'length' => self::CONTENT_LENGTH, 104 | 'sha2' => self::CONTENT_SHA2, 105 | 'fileUrl' => self::FILE_URL 106 | ]; 107 | $obj = new Attachment($args); 108 | $versioned = $obj->asVersion(Version::latest()); 109 | $this->assertEquals($versioned, $args, '1.0.0'); 110 | 111 | $obj = new Attachment( 112 | [ 113 | 'content' => self::CONTENT_STR 114 | ] 115 | ); 116 | $this->assertEquals( 117 | $obj->asVersion(Version::latest()), 118 | ['length' => self::CONTENT_LENGTH, 'sha2' => self::CONTENT_SHA2], 119 | 'auto populated properties but content not returned' 120 | ); 121 | } 122 | 123 | public function testAsVersionEmpty() { 124 | $args = []; 125 | 126 | $obj = Attachment::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 127 | $versioned = $obj->asVersion('1.0.0'); 128 | 129 | $this->assertEquals($versioned, $args, "serialized version matches original"); 130 | } 131 | 132 | public function testAsVersionEmptyLanguageMap() { 133 | $args = ['display' => []]; 134 | 135 | $obj = Attachment::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 136 | $versioned = $obj->asVersion('1.0.0'); 137 | 138 | unset($args['display']); 139 | 140 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 141 | } 142 | 143 | public function testCompareWithSignature() { 144 | $full = [ 145 | 'usageType' => self::USAGE_TYPE, 146 | 'display' => ['en-US' => self::DISPLAY], 147 | 'description' => ['en-US' => self::DESCRIPTION], 148 | 'contentType' => self::CONTENT_TYPE, 149 | 'length' => self::CONTENT_LENGTH, 150 | 'sha2' => self::CONTENT_SHA2, 151 | 'fileUrl' => self::FILE_URL 152 | ]; 153 | 154 | $cases = [ 155 | [ 156 | 'description' => 'all null', 157 | 'objArgs' => [] 158 | ], 159 | [ 160 | 'description' => 'usageType', 161 | 'objArgs' => ['usageType' => self::USAGE_TYPE] 162 | ], 163 | [ 164 | 'description' => 'display', 165 | 'objArgs' => ['display' => self::DISPLAY] 166 | ], 167 | [ 168 | 'description' => 'description', 169 | 'objArgs' => ['description' => self::DESCRIPTION] 170 | ], 171 | [ 172 | 'description' => 'contentType', 173 | 'objArgs' => ['contentType' => self::CONTENT_TYPE] 174 | ], 175 | [ 176 | 'description' => 'length', 177 | 'objArgs' => ['length' => self::CONTENT_LENGTH] 178 | ], 179 | [ 180 | 'description' => 'sha2', 181 | 'objArgs' => ['sha2' => self::CONTENT_SHA2] 182 | ], 183 | [ 184 | 'description' => 'fileUrl', 185 | 'objArgs' => ['fileUrl' => self::FILE_URL] 186 | ], 187 | [ 188 | 'description' => 'all', 189 | 'objArgs' => $full 190 | ], 191 | 192 | // 193 | // display and description are language maps which we aren't 194 | // checking for meaningful differences, so even though they 195 | // differ they should not cause signature comparison to fail 196 | // 197 | [ 198 | 'description' => 'display only with difference', 199 | 'objArgs' => ['display' => [ 'en-US' => self::DISPLAY ]], 200 | 'sigArgs' => ['display' => [ 'en-US' => self::DISPLAY . ' invalid' ]] 201 | ], 202 | [ 203 | 'description' => 'description only with difference', 204 | 'objArgs' => ['description' => [ 'en-US' => self::DESCRIPTION ]], 205 | 'sigArgs' => ['description' => [ 'en-US' => self::DESCRIPTION . ' invalid' ]] 206 | ], 207 | 208 | [ 209 | 'description' => 'usageType only: mismatch', 210 | 'objArgs' => ['usageType' => self::USAGE_TYPE ], 211 | 'sigArgs' => ['usageType' => self::USAGE_TYPE . '/invalid' ], 212 | 'reason' => 'Comparison of usageType failed: value is not the same' 213 | ], 214 | [ 215 | 'description' => 'contentType only: mismatch', 216 | 'objArgs' => ['contentType' => self::CONTENT_TYPE ], 217 | 'sigArgs' => ['contentType' => 'application/octet-stream' ], 218 | 'reason' => 'Comparison of contentType failed: value is not the same' 219 | ], 220 | [ 221 | 'description' => 'length only: mismatch', 222 | 'objArgs' => ['length' => self::CONTENT_LENGTH ], 223 | 'sigArgs' => ['length' => self::CONTENT_LENGTH + 2 ], 224 | 'reason' => 'Comparison of length failed: value is not the same' 225 | ], 226 | [ 227 | 'description' => 'sha2 only: mismatch', 228 | 'objArgs' => ['sha2' => self::CONTENT_SHA2], 229 | 'sigArgs' => ['sha2' => self::CONTENT_SHA2 . self::CONTENT_SHA2], 230 | 'reason' => 'Comparison of sha2 failed: value is not the same' 231 | ], 232 | [ 233 | 'description' => 'fileUrl only: mismatch', 234 | 'objArgs' => ['fileUrl' => self::FILE_URL], 235 | 'sigArgs' => ['fileUrl' => self::FILE_URL . '/invalid'], 236 | 'reason' => 'Comparison of fileUrl failed: value is not the same' 237 | ], 238 | [ 239 | 'description' => 'full: usageType mismatch', 240 | 'objArgs' => $full, 241 | 'sigArgs' => array_replace($full, ['usageType' => self::USAGE_TYPE . '/invalid']), 242 | 'reason' => 'Comparison of usageType failed: value is not the same' 243 | ], 244 | [ 245 | 'description' => 'full: contentType mismatch', 246 | 'objArgs' => $full, 247 | 'sigArgs' => array_replace($full, ['contentType' => 'application/octet-stream']), 248 | 'reason' => 'Comparison of contentType failed: value is not the same' 249 | ], 250 | [ 251 | 'description' => 'full: length mismatch', 252 | 'objArgs' => $full, 253 | 'sigArgs' => array_replace($full, ['length' => self::CONTENT_LENGTH + 2]), 254 | 'reason' => 'Comparison of length failed: value is not the same' 255 | ], 256 | [ 257 | 'description' => 'full: sha2 mismatch', 258 | 'objArgs' => $full, 259 | 'sigArgs' => array_replace($full, ['sha2' => self::CONTENT_SHA2 . self::CONTENT_SHA2]), 260 | 'reason' => 'Comparison of sha2 failed: value is not the same' 261 | ], 262 | [ 263 | 'description' => 'full: fileUrl mismatch', 264 | 'objArgs' => $full, 265 | 'sigArgs' => array_replace($full, ['fileUrl' => self::FILE_URL . '/invalid']), 266 | 'reason' => 'Comparison of fileUrl failed: value is not the same' 267 | ] 268 | ]; 269 | $this->runSignatureCases("TinCan\Attachment", $cases); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /tests/DocumentTest.php: -------------------------------------------------------------------------------- 1 | setExpectedException( 27 | "InvalidArgumentException", 28 | 'type of arg1 must be string or DateTime' 29 | ); 30 | 31 | $obj = new StubDocument(); 32 | $obj->setTimestamp(1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/ExtensionsTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\Extensions', $obj); 28 | } 29 | 30 | public function testAsVersion() { 31 | $args = []; 32 | $args[COMMON_EXTENSION_ID_1] = 'test'; 33 | $args[COMMON_EXTENSION_ID_2] = 'test2'; 34 | 35 | $obj = new Extensions($args); 36 | $versioned = $obj->asVersion('1.0.0'); 37 | 38 | $this->assertEquals($versioned, $args, "version: 1.0.0"); 39 | } 40 | 41 | public function testAsVersionEmpty() { 42 | $args = []; 43 | 44 | $obj = Extensions::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 45 | $versioned = $obj->asVersion('1.0.0'); 46 | 47 | $this->assertEquals($versioned, null, "serialized version matches original"); 48 | } 49 | 50 | public function testCompareWithSignature() { 51 | $success = ['success' => true, 'reason' => null]; 52 | 53 | $content_1 = 'some value'; 54 | $content_2 = 'some other value'; 55 | 56 | $extensions1 = [ COMMON_EXTENSION_ID_1 => 'some value' ]; 57 | $extensions2 = [ COMMON_EXTENSION_ID_1 => 'some value', COMMON_EXTENSION_ID_2 => 'some other value' ]; 58 | $extensions3 = [ COMMON_EXTENSION_ID_2 => 'some other value' ]; 59 | 60 | $cases = [ 61 | [ 62 | 'description' => 'empty', 63 | 'objArgs' => [] 64 | ], 65 | [ 66 | 'description' => 'single', 67 | 'objArgs' => $extensions1 68 | ], 69 | [ 70 | 'description' => 'multiple', 71 | 'objArgs' => $extensions2 72 | ], 73 | [ 74 | 'description' => 'empty sig: mismatch', 75 | 'objArgs' => $extensions1, 76 | 'sigArgs' => [], 77 | 'reason' => 'http://id.tincanapi.com/extension/topic not in signature' 78 | ], 79 | [ 80 | 'description' => 'empty this: mismatch', 81 | 'objArgs' => [], 82 | 'sigArgs' => $extensions1, 83 | 'reason' => 'http://id.tincanapi.com/extension/topic not in this' 84 | ], 85 | [ 86 | 'description' => 'missing in sig: mismatch', 87 | 'objArgs' => $extensions2, 88 | 'sigArgs' => $extensions3, 89 | 'reason' => 'http://id.tincanapi.com/extension/topic not in signature' 90 | ], 91 | [ 92 | 'description' => 'missing in this: mismatch', 93 | 'objArgs' => $extensions3, 94 | 'sigArgs' => $extensions2, 95 | 'reason' => 'http://id.tincanapi.com/extension/topic not in this' 96 | ], 97 | [ 98 | 'description' => 'single diff value in sig: mismatch', 99 | 'objArgs' => $extensions2, 100 | 'sigArgs' => array_replace($extensions2, [COMMON_EXTENSION_ID_1 => 'diff']), 101 | 'reason' => 'http://id.tincanapi.com/extension/topic does not match' 102 | ], 103 | [ 104 | 'description' => 'single diff value in this: mismatch', 105 | 'objArgs' => array_replace($extensions2, [COMMON_EXTENSION_ID_1 => 'diff']), 106 | 'sigArgs' => $extensions2, 107 | 'reason' => 'http://id.tincanapi.com/extension/topic does not match' 108 | ] 109 | ]; 110 | $this->runSignatureCases("TinCan\Extensions", $cases); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/ISO8601Test.php: -------------------------------------------------------------------------------- 1 | setDate(2014, 12, 15); 32 | $datetime->setTime(19, 16, 05); 33 | 34 | $statement = new Statement(); 35 | $statement->setStored($datetime); 36 | $statement->setTimestamp($datetime); 37 | 38 | $document = new State(); 39 | $document->setTimestamp($datetime); 40 | 41 | $this->assertEquals($statement->getStored(), $str_datetime, 'stored matches'); 42 | $this->assertEquals($statement->getTimestamp(), $str_datetime, 'timestamp matches'); 43 | $this->assertEquals($document->getTimestamp(), $str_datetime, 'document timestamp matches'); 44 | 45 | $datetime->setTimezone(new DateTimeZone('America/Chicago')); 46 | $statement->setStored($datetime); 47 | $statement->setTimestamp($datetime); 48 | $document->setTimestamp($datetime); 49 | 50 | $this->assertEquals($statement->getStored(), $str_datetime_tz, 'stored matches'); 51 | $this->assertEquals($statement->getTimestamp(), $str_datetime_tz, 'timestamp matches'); 52 | $this->assertEquals($document->getTimestamp(), $str_datetime_tz, 'document timestamp matches'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/JSONParseErrorExceptionTest.php: -------------------------------------------------------------------------------- 1 | jsonErrorNumber = JSON_ERROR_SYNTAX; 29 | $this->jsonErrorMessage = 'Syntax error, malformed JSON'; 30 | $this->exception = new JSONParseErrorException( 31 | $this->malformedValue, 32 | $this->jsonErrorNumber, 33 | $this->jsonErrorMessage 34 | ); 35 | } 36 | 37 | public function testFetchErrorInformation() { 38 | $this->assertEquals($this->malformedValue, $this->exception->malformedValue()); 39 | $this->assertEquals($this->jsonErrorNumber, $this->exception->jsonErrorNumber()); 40 | $this->assertEquals($this->jsonErrorMessage, $this->exception->jsonErrorMessage()); 41 | } 42 | 43 | public function testGetMessage() { 44 | $format = 'Invalid JSON "%s": %s (%d)'; 45 | $expected = sprintf($format, $this->malformedValue, $this->jsonErrorMessage, $this->jsonErrorNumber); 46 | $this->assertEquals($expected, $this->exception->getMessage()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/LRSResponseTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($obj->success); 26 | $this->assertEquals('', $obj->content); 27 | $this->assertFalse($obj->httpResponse); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/LanguageMapTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\LanguageMap', $obj); 28 | } 29 | 30 | public function testAsVersion() { 31 | $args = ['en' => [self::NAME]]; 32 | 33 | $obj = LanguageMap::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 34 | $versioned = $obj->asVersion(); 35 | 36 | $this->assertEquals($versioned, $args, "serialized version matches original"); 37 | } 38 | 39 | public function testAsVersionEmpty() { 40 | $args = []; 41 | 42 | $obj = LanguageMap::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 43 | $versioned = $obj->asVersion('1.0.0'); 44 | 45 | $this->assertEquals($versioned, null, "serialization returns null"); 46 | } 47 | 48 | public function testAsVersionValueEmptyString() { 49 | $args = ['en' => ['']]; 50 | 51 | $obj = LanguageMap::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 52 | $versioned = $obj->asVersion(); 53 | 54 | $this->assertEquals($versioned, $args, "serialized version matches original"); 55 | } 56 | 57 | public function testGetNegotiatedLanguageString() { 58 | $langs = [ 59 | 'en-GB' => 'petrol', 60 | 'en-US' => 'gasoline' 61 | ]; 62 | $obj = new LanguageMap($langs); 63 | 64 | $usValue = $obj->getNegotiatedLanguageString('en-US;q=0.8, en-GB;q=0.6'); 65 | $ukValue = $obj->getNegotiatedLanguageString('en-GB;q=0.8, en-US;q=0.6'); 66 | 67 | $this->assertEquals($usValue, $langs['en-US'], 'US name equal'); 68 | $this->assertEquals($ukValue, $langs['en-GB'], 'UK name equal'); 69 | 70 | $nullValue = $obj->getNegotiatedLanguageString(); 71 | $this->assertEquals($nullValue, $langs['en-GB'], 'from null: UK name equal'); 72 | 73 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 74 | $restore = $_SERVER['HTTP_ACCEPT_LANGUAGE']; 75 | } 76 | $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US'; 77 | 78 | $nullAcceptValue = $obj->getNegotiatedLanguageString(); 79 | $this->assertEquals($nullAcceptValue, $langs['en-US'], 'from server: US name equal'); 80 | 81 | if (isset($restore)) { 82 | $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $restore; 83 | } 84 | else { 85 | unset($_SERVER['HTTP_ACCEPT_LANGUAGE']); 86 | } 87 | 88 | $langs = [ 89 | 'en-US' => 'gasoline' 90 | ]; 91 | $obj = new LanguageMap($langs); 92 | 93 | $this->assertEquals($obj->getNegotiatedLanguageString('en'), $langs['en-US'], 'from prefix'); 94 | 95 | $langs = [ 96 | 'fr-FR' => 'essence' 97 | ]; 98 | $obj = new LanguageMap($langs); 99 | 100 | $this->assertEquals($obj->getNegotiatedLanguageString('en, *'), $langs['fr-FR'], 'no matched'); 101 | 102 | $langs = [ 103 | 'fr-FR' => 'essence', 104 | 'und' => 'fuel', 105 | ]; 106 | $obj = new LanguageMap($langs); 107 | 108 | $this->assertEquals($obj->getNegotiatedLanguageString('en'), $langs['und'], 'no matched with und'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/MapTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($obj->isEmpty()); 32 | } 33 | 34 | public function testSetUnset() { 35 | $obj = new StubMap(); 36 | 37 | $code = 'code'; 38 | $value = 'value'; 39 | 40 | $obj->set($code, $value); 41 | 42 | $this->assertEquals($value, $obj->asVersion()[$code]); 43 | 44 | $obj->unset($code); 45 | 46 | $this->assertFalse(isset($obj->asVersion()[$code])); 47 | } 48 | 49 | public function testExceptionOnBadMethodCall() { 50 | $badName ="dsadasdasdasdasdasdas"; 51 | 52 | $this->setExpectedException( 53 | '\BadMethodCallException', 54 | get_class(new StubMap) . "::$badName() does not exist" 55 | ); 56 | 57 | $obj = new StubMap(); 58 | $obj->$badName(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/PersonTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\Person', $obj); 28 | $this->assertAttributeEmpty('name', $obj, 'name empty'); 29 | $this->assertAttributeEmpty('mbox', $obj, 'mbox empty'); 30 | $this->assertAttributeEmpty('mbox_sha1sum', $obj, 'mbox_sha1sum empty'); 31 | $this->assertAttributeEmpty('openid', $obj, 'openid empty'); 32 | $this->assertAttributeEmpty('account', $obj, 'account empty'); 33 | } 34 | 35 | public function testFromJSONInvalidNull() { 36 | $this->setExpectedException('TinCan\JSONParseErrorException'); 37 | $obj = Person::fromJSON(null); 38 | } 39 | 40 | public function testFromJSONInvalidEmptyString() { 41 | $this->setExpectedException('TinCan\JSONParseErrorException'); 42 | $obj = Person::fromJSON(''); 43 | } 44 | 45 | public function testFromJSONInvalidMalformed() { 46 | $this->setExpectedException('TinCan\JSONParseErrorException'); 47 | $obj = Person::fromJSON('{name:"some value"}'); 48 | } 49 | 50 | // TODO: need to loop possible configs 51 | public function testFromJSONInstantiations() { 52 | $obj = Person::fromJSON('{"mbox":["' . COMMON_MBOX . '","'.COMMON_MBOX.'"]}'); 53 | $this->assertInstanceOf('TinCan\Person', $obj); 54 | $this->assertSame(array(COMMON_MBOX, COMMON_MBOX), $obj->getMbox(), 'mbox value'); 55 | } 56 | 57 | // TODO: need to loop versions 58 | public function testAsVersion() { 59 | $obj = new Person( 60 | [ 61 | 'mbox' => array(COMMON_MBOX), 62 | 'account' => array( 63 | array( 64 | 'name' => COMMON_ACCT_NAME, 65 | 'homePage' => COMMON_ACCT_HOMEPAGE 66 | ) 67 | ) 68 | ] 69 | ); 70 | $versioned = $obj->asVersion('1.0.0'); 71 | 72 | $this->assertEquals( 73 | [ 74 | 'objectType' => 'Person', 75 | 'mbox' => array(COMMON_MBOX), 76 | 'account' => array( 77 | array( 78 | 'name' => COMMON_ACCT_NAME, 79 | 'homePage' => COMMON_ACCT_HOMEPAGE 80 | ) 81 | ) 82 | ], 83 | $versioned, 84 | "mbox only: 1.0.0" 85 | ); 86 | } 87 | 88 | public function testSetMbox() { 89 | $obj = new Person(); 90 | 91 | $obj->setMbox(array(COMMON_MBOX)); 92 | $this->assertSame(array(COMMON_MBOX), $obj->getMbox()); 93 | 94 | // 95 | // make sure it doesn't add mailto when null 96 | // 97 | $obj->setMbox(null); 98 | $this->assertAttributeEmpty('mbox', $obj); 99 | } 100 | 101 | public function testGetMbox_sha1sum() { 102 | $obj = new Person(['mbox_sha1sum' => array(COMMON_MBOX_SHA1)]); 103 | $this->assertSame($obj->getMbox_sha1sum(), array(COMMON_MBOX_SHA1), 'original sha1'); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/ResultTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\Result', $obj); 42 | foreach ($this->emptyProperties as $property) { 43 | $this->assertAttributeEmpty($property, $obj, "$property empty"); 44 | } 45 | foreach ($this->nonEmptyProperties as $property) { 46 | $this->assertAttributeNotEmpty($property, $obj, "$property not empty"); 47 | } 48 | } 49 | 50 | public function testUsesArraySetterTrait() { 51 | $this->assertContains('TinCan\ArraySetterTrait', class_uses('TinCan\Result')); 52 | } 53 | 54 | public function testUsesFromJSONTrait() { 55 | $this->assertContains('TinCan\FromJSONTrait', class_uses('TinCan\Result')); 56 | } 57 | 58 | public function testUsesAsVersionTrait() { 59 | $this->assertContains('TinCan\AsVersionTrait', class_uses('TinCan\Result')); 60 | } 61 | 62 | // TODO: need more robust test (happy-path) 63 | public function testAsVersion() { 64 | $args = [ 65 | 'success' => true, 66 | 'completion' => true, 67 | 'duration' => 'PT15S', 68 | 'response' => 'a', 69 | 'score' => [ 70 | 'raw' => 100, 71 | 'scaled' => 1, 72 | 'min' => 0, 73 | 'max' => 100 74 | ] 75 | ]; 76 | $obj = new Result($args); 77 | $versioned = $obj->asVersion('1.0.0'); 78 | 79 | $this->assertEquals($versioned, $args, "serialized version matches original"); 80 | } 81 | 82 | public function testAsVersionEmpty() { 83 | $args = []; 84 | 85 | $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 86 | $versioned = $obj->asVersion('1.0.0'); 87 | 88 | $this->assertEquals($versioned, $args, "serialized version matches original"); 89 | } 90 | 91 | public function testAsVersionScoreEmpty() { 92 | $args = [ 93 | 'score' => [] 94 | ]; 95 | 96 | $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 97 | $versioned = $obj->asVersion('1.0.0'); 98 | 99 | unset($args['score']); 100 | 101 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 102 | } 103 | 104 | public function testAsVersionScoreZeroRaw() { 105 | $args = [ 106 | 'score' => [ 107 | 'raw' => 0, 108 | ] 109 | ]; 110 | 111 | $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 112 | $versioned = $obj->asVersion('1.0.0'); 113 | 114 | $this->assertEquals($versioned, $args, "serialized version matches original"); 115 | } 116 | 117 | public function testAsVersionResponseEmptyString() { 118 | $args = [ 119 | 'response' => '' 120 | ]; 121 | 122 | $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 123 | $versioned = $obj->asVersion('1.0.0'); 124 | 125 | $this->assertEquals($versioned, $args, "serialized version matches original"); 126 | } 127 | 128 | public function testAsVersionDurationEmptyString() { 129 | $args = [ 130 | 'duration' => '' 131 | ]; 132 | 133 | $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 134 | $versioned = $obj->asVersion('1.0.0'); 135 | 136 | unset($args['duration']); 137 | 138 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 139 | } 140 | 141 | public function testCompareWithSignature() { 142 | $score1 = new Score( 143 | [ 144 | 'raw' => 97, 145 | 'scaled' => 0.97, 146 | 'min' => 0, 147 | 'max' => 100 148 | ] 149 | ); 150 | $score2 = new Score( 151 | [ 152 | 'raw' => 15, 153 | 'scaled' => 0.50 154 | ] 155 | ); 156 | $extensions1 = new Extensions( 157 | [ 158 | COMMON_EXTENSION_ID_1 => 'test1', 159 | COMMON_EXTENSION_ID_2 => 'test2' 160 | ] 161 | ); 162 | $extensions2 = new Extensions( 163 | [ 164 | COMMON_EXTENSION_ID_1 => 'test1' 165 | ] 166 | ); 167 | 168 | $full = [ 169 | 'success' => true, 170 | 'completion' => true, 171 | 'duration' => 'PT15S', 172 | 'response' => 'a', 173 | 'score' => $score1, 174 | 'extensions' => $extensions1 175 | ]; 176 | 177 | $cases = [ 178 | [ 179 | 'description' => 'all null', 180 | 'objArgs' => [] 181 | ], 182 | [ 183 | 'description' => 'success', 184 | 'objArgs' => ['success' => true] 185 | ], 186 | [ 187 | 'description' => 'completion', 188 | 'objArgs' => ['completion' => true] 189 | ], 190 | [ 191 | 'description' => 'duration', 192 | 'objArgs' => ['duration' => 'PT15S'] 193 | ], 194 | [ 195 | 'description' => 'response', 196 | 'objArgs' => ['response' => 'a'] 197 | ], 198 | [ 199 | 'description' => 'score', 200 | 'objArgs' => ['score' => $score1] 201 | ], 202 | [ 203 | 'description' => 'extensions', 204 | 'objArgs' => ['extensions' => $extensions1] 205 | ], 206 | [ 207 | 'description' => 'all', 208 | 'objArgs' => [ 209 | 'success' => false, 210 | 'completion' => false, 211 | 'duration' => 'PT15S', 212 | 'response' => 'b', 213 | 'score' => $score2, 214 | 'extensions' => $extensions2 215 | ] 216 | ], 217 | [ 218 | 'description' => 'success only: mismatch', 219 | 'objArgs' => ['success' => true ], 220 | 'sigArgs' => ['success' => false ], 221 | 'reason' => 'Comparison of success failed: value is not the same' 222 | ], 223 | [ 224 | 'description' => 'completion only: mismatch', 225 | 'objArgs' => ['completion' => true ], 226 | 'sigArgs' => ['completion' => false ], 227 | 'reason' => 'Comparison of completion failed: value is not the same' 228 | ], 229 | [ 230 | 'description' => 'duration only: mismatch', 231 | 'objArgs' => ['duration' => 'PT15S' ], 232 | 'sigArgs' => ['duration' => 'PT180S' ], 233 | 'reason' => 'Comparison of duration failed: value is not the same' 234 | ], 235 | [ 236 | 'description' => 'response only: mismatch', 237 | 'objArgs' => ['response' => 'a' ], 238 | 'sigArgs' => ['response' => 'b' ], 239 | 'reason' => 'Comparison of response failed: value is not the same' 240 | ], 241 | [ 242 | 'description' => 'score only: mismatch', 243 | 'objArgs' => ['score' => $score1 ], 244 | 'sigArgs' => ['score' => $score2 ], 245 | 'reason' => 'Comparison of score failed: Comparison of scaled failed: value is not the same' 246 | ], 247 | [ 248 | 'description' => 'extensions only: mismatch', 249 | 'objArgs' => ['extensions' => $extensions1 ], 250 | 'sigArgs' => ['extensions' => $extensions2 ], 251 | 'reason' => 'Comparison of extensions failed: http://id.tincanapi.com/extension/location not in signature' 252 | ], 253 | [ 254 | 'description' => 'full: success mismatch', 255 | 'objArgs' => $full, 256 | 'sigArgs' => array_replace($full, ['success' => false]), 257 | 'reason' => 'Comparison of success failed: value is not the same' 258 | ], 259 | [ 260 | 'description' => 'full: completion mismatch', 261 | 'objArgs' => $full, 262 | 'sigArgs' => array_replace($full, ['completion' => false]), 263 | 'reason' => 'Comparison of completion failed: value is not the same' 264 | ], 265 | [ 266 | 'description' => 'full: duration mismatch', 267 | 'objArgs' => $full, 268 | 'sigArgs' => array_replace($full, ['duration' => 'PT150S']), 269 | 'reason' => 'Comparison of duration failed: value is not the same' 270 | ], 271 | [ 272 | 'description' => 'full: response mismatch', 273 | 'objArgs' => $full, 274 | 'sigArgs' => array_replace($full, ['response' => 'b']), 275 | 'reason' => 'Comparison of response failed: value is not the same' 276 | ], 277 | [ 278 | 'description' => 'full: score mismatch', 279 | 'objArgs' => $full, 280 | 'sigArgs' => array_replace($full, ['score' => $score2]), 281 | 'reason' => 'Comparison of score failed: Comparison of scaled failed: value is not the same' 282 | ], 283 | [ 284 | 'description' => 'full: extensions mismatch', 285 | 'objArgs' => $full, 286 | 'sigArgs' => array_replace($full, ['extensions' => $extensions2]), 287 | 'reason' => 'Comparison of extensions failed: http://id.tincanapi.com/extension/location not in signature' 288 | ] 289 | ]; 290 | $this->runSignatureCases("TinCan\Result", $cases); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /tests/ScoreTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\Score', $obj); 35 | foreach ($this->emptyProperties as $property) { 36 | $this->assertAttributeEmpty($property, $obj, "$property empty"); 37 | } 38 | } 39 | 40 | public function testUsesArraySetterTrait() { 41 | $this->assertContains('TinCan\ArraySetterTrait', class_uses('TinCan\Score')); 42 | } 43 | 44 | public function testUsesFromJSONTrait() { 45 | $this->assertContains('TinCan\FromJSONTrait', class_uses('TinCan\Score')); 46 | } 47 | 48 | public function testUsesAsVersionTrait() { 49 | $this->assertContains('TinCan\AsVersionTrait', class_uses('TinCan\Score')); 50 | } 51 | 52 | public function testScaled() { 53 | $score = new Score; 54 | $score->setScaled(0.9); 55 | 56 | $this->assertEquals($score->getScaled(), 0.9); 57 | $this->assertInternalType('float', $score->getScaled()); 58 | } 59 | 60 | public function testSetScaledBelowMin() { 61 | $this->setExpectedException( 62 | 'InvalidArgumentException', 63 | sprintf('Value must be greater than or equal to %s [-5]', Score::SCALE_MIN) 64 | ); 65 | $score = new Score; 66 | $score->setScaled(-5); 67 | } 68 | 69 | public function testSetScaledAboveMax() { 70 | $this->setExpectedException( 71 | 'InvalidArgumentException', 72 | sprintf('Value must be less than or equal to %s [5]', Score::SCALE_MAX) 73 | ); 74 | $score = new Score; 75 | $score->setScaled(5); 76 | } 77 | 78 | public function testRaw() { 79 | $score = new Score; 80 | $score->setRaw(90); 81 | 82 | $this->assertEquals($score->getRaw(), 90); 83 | $this->assertInternalType('float', $score->getRaw()); 84 | 85 | $score = new Score(['min' => 65, 'max' => 85]); 86 | $score->setRaw(75); 87 | $this->assertEquals($score->getRaw(), 75, 'between min and max'); 88 | 89 | $score = new Score(['min' => 65]); 90 | $score->setRaw(65); 91 | $this->assertEquals($score->getRaw(), 65, 'same as min'); 92 | 93 | $score = new Score(['max' => 65]); 94 | $score->setRaw(65); 95 | $this->assertEquals($score->getRaw(), 65, 'same as max'); 96 | } 97 | 98 | public function testSetRawBelowMin() { 99 | $this->setExpectedException( 100 | 'InvalidArgumentException', 101 | 'Value must be greater than or equal to \'min\' (60) [50]' 102 | ); 103 | $score = new Score(['min' => 60]); 104 | $score->setRaw(50); 105 | } 106 | 107 | public function testSetRawAboveMax() { 108 | $this->setExpectedException( 109 | 'InvalidArgumentException', 110 | 'Value must be less than or equal to \'max\' (90) [95]' 111 | ); 112 | $score = new Score(['max' => 90]); 113 | $score->setRaw(95); 114 | } 115 | 116 | public function testMin() { 117 | $score = new Score; 118 | $score->setMin(9); 119 | 120 | $this->assertEquals($score->getMin(), 9); 121 | $this->assertInternalType('float', $score->getMin()); 122 | 123 | $score = new Score(['raw' => 65, 'max' => 85]); 124 | $score->setMin(35); 125 | $this->assertEquals($score->getMin(), 35, 'below raw'); 126 | 127 | $score = new Score(['raw' => 35, 'max' => 85]); 128 | $score->setMin(35); 129 | $this->assertEquals($score->getMin(), 35, 'equal to raw'); 130 | } 131 | 132 | public function testSetMinAboveRaw() { 133 | $this->setExpectedException( 134 | 'InvalidArgumentException', 135 | 'Value must be less than or equal to \'raw\' (50) [60]' 136 | ); 137 | $score = new Score(['raw' => 50]); 138 | $score->setMin(60); 139 | } 140 | 141 | public function testSetMinAboveMax() { 142 | $this->setExpectedException( 143 | 'InvalidArgumentException', 144 | 'Value must be less than \'max\' (90) [95]' 145 | ); 146 | $score = new Score(['max' => 90]); 147 | $score->setMin(95); 148 | } 149 | 150 | public function testMax() { 151 | $score = new Score; 152 | $score->setMax(96.3); 153 | 154 | $this->assertEquals($score->getMax(), 96.3); 155 | $this->assertInternalType('float', $score->getMax()); 156 | 157 | $score = new Score(['raw' => 65, 'min' => 35]); 158 | $score->setMax(85.4); 159 | $this->assertEquals($score->getMax(), 85.4, 'above raw'); 160 | 161 | $score = new Score(['raw' => 35, 'min' => 15]); 162 | $score->setMax(35); 163 | $this->assertEquals($score->getMax(), 35, 'equal to raw'); 164 | } 165 | 166 | public function testSetMaxBelowRaw() { 167 | $this->setExpectedException( 168 | 'InvalidArgumentException', 169 | 'Value must be greater than or equal to \'raw\' (60) [50]' 170 | ); 171 | $score = new Score(['raw' => 60]); 172 | $score->setMax(50); 173 | } 174 | 175 | public function testSetMaxBelowMin() { 176 | $this->setExpectedException( 177 | 'InvalidArgumentException', 178 | 'Value must be greater than \'min\' (10) [5]' 179 | ); 180 | $score = new Score(['min' => 10]); 181 | $score->setMax(5); 182 | } 183 | 184 | /** 185 | * @dataProvider asVersionDataProvider 186 | */ 187 | public function testAsVersion($args) { 188 | $obj = new Score($args); 189 | $versioned = $obj->asVersion('1.0.0'); 190 | 191 | $this->assertEquals($versioned, $args, "version: 1.0.0"); 192 | } 193 | 194 | public function asVersionDataProvider() { 195 | return [ 196 | 'basic' => [ 197 | [ 198 | 'raw' => '1.5', 199 | 'min' => '1.0', 200 | 'max' => '2.0', 201 | 'scaled' => '.95' 202 | ] 203 | ], 204 | 'empty' => [[]], 205 | 'zero raw' => [ 206 | [ 'raw' => 0 ] 207 | ], 208 | 'zero scaled' => [ 209 | [ 'scaled' => 0 ] 210 | ], 211 | 'multiple zeros' => [ 212 | [ 213 | 'raw' => '0', 214 | 'min' => '-1.0', 215 | 'max' => 2.0, 216 | 'scaled' => 0 217 | ] 218 | ] 219 | ]; 220 | } 221 | 222 | public function testCompareWithSignature() { 223 | $full = [ 224 | 'raw' => 97, 225 | 'scaled' => 0.97, 226 | 'min' => 0, 227 | 'max' => 100 228 | ]; 229 | $cases = [ 230 | [ 231 | 'description' => 'all null', 232 | 'objArgs' => [] 233 | ], 234 | [ 235 | 'description' => 'raw', 236 | 'objArgs' => ['raw' => 97] 237 | ], 238 | [ 239 | 'description' => 'scaled', 240 | 'objArgs' => ['scaled' => 0.97] 241 | ], 242 | [ 243 | 'description' => 'min', 244 | 'objArgs' => ['min' => 60] 245 | ], 246 | [ 247 | 'description' => 'max', 248 | 'objArgs' => ['max' => 99] 249 | ], 250 | [ 251 | 'description' => 'all', 252 | 'objArgs' => $full 253 | ], 254 | [ 255 | 'description' => 'raw only: mismatch', 256 | 'objArgs' => ['raw' => 97 ], 257 | 'sigArgs' => ['raw' => 87 ], 258 | 'reason' => 'Comparison of raw failed: value is not the same' 259 | ], 260 | [ 261 | 'description' => 'scaled only: mismatch', 262 | 'objArgs' => ['scaled' => 0.97 ], 263 | 'sigArgs' => ['scaled' => 0.87 ], 264 | 'reason' => 'Comparison of scaled failed: value is not the same' 265 | ], 266 | [ 267 | 'description' => 'min only: mismatch', 268 | 'objArgs' => ['min' => 0 ], 269 | 'sigArgs' => ['min' => 1 ], 270 | 'reason' => 'Comparison of min failed: value is not the same' 271 | ], 272 | [ 273 | 'description' => 'max only: mismatch', 274 | 'objArgs' => ['max' => 97 ], 275 | 'sigArgs' => ['max' => 100 ], 276 | 'reason' => 'Comparison of max failed: value is not the same' 277 | ], 278 | [ 279 | 'description' => 'full: raw mismatch', 280 | 'objArgs' => $full, 281 | 'sigArgs' => array_replace($full, ['raw' => 79]), 282 | 'reason' => 'Comparison of raw failed: value is not the same' 283 | ], 284 | [ 285 | 'description' => 'full: scaled mismatch', 286 | 'objArgs' => $full, 287 | 'sigArgs' => array_replace($full, ['scaled' => 0.96]), 288 | 'reason' => 'Comparison of scaled failed: value is not the same' 289 | ], 290 | [ 291 | 'description' => 'full: min mismatch', 292 | 'objArgs' => $full, 293 | 'sigArgs' => array_replace($full, ['min' => 1]), 294 | 'reason' => 'Comparison of min failed: value is not the same' 295 | ], 296 | [ 297 | 'description' => 'full: max mismatch', 298 | 'objArgs' => $full, 299 | 'sigArgs' => array_replace($full, ['max' => 98]), 300 | 'reason' => 'Comparison of max failed: value is not the same' 301 | ] 302 | ]; 303 | $this->runSignatureCases("TinCan\Score", $cases); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /tests/SignatureComparisonTraitTest.php: -------------------------------------------------------------------------------- 1 | assertFalse($result['success']); 36 | $this->assertEquals("Comparison of $description failed: not a " . get_class($a) . " value", $result['reason']); 37 | 38 | $result = SignatureComparisonStub::runDoMatch([], false, $description); 39 | 40 | $this->assertFalse($result['success']); 41 | $this->assertEquals("Comparison of $description failed: not an array in signature", $result['reason']); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/StateTest.php: -------------------------------------------------------------------------------- 1 | COMMON_ACTIVITY_ID, 27 | 'definition' => [] 28 | ]; 29 | 30 | $state = new State(); 31 | $state->setActivity($args); 32 | } 33 | 34 | public function testSetAgent() { 35 | $obj = new State(); 36 | $obj->setAgent(['mbox' => COMMON_MBOX]); 37 | 38 | $this->assertInstanceOf('TinCan\Agent', $obj->getAgent()); 39 | 40 | $group = new Group(); 41 | $obj->setAgent(['objectType' => 'Group']); 42 | 43 | $this->assertInstanceOf('TinCan\Group', $obj->getAgent()); 44 | } 45 | 46 | public function testExceptionOnInvalidRegistrationUUID() { 47 | $this->setExpectedException( 48 | "InvalidArgumentException", 49 | 'arg1 must be a UUID' 50 | ); 51 | 52 | $obj = new State(); 53 | $obj->setRegistration('232....3.3..3./2/2/1m3m3m3'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/StatementBaseTest.php: -------------------------------------------------------------------------------- 1 | 'Agent' 38 | ]; 39 | $obj->setTarget($ss); 40 | $this->assertInstanceOf('TinCan\Agent', $obj->getTarget()); 41 | } 42 | 43 | public function testSetTargetAsGroup() { 44 | $obj = new StubStatementBase(); 45 | $ss = [ 46 | 'objectType' => 'Group' 47 | ]; 48 | $obj->setTarget($ss); 49 | $this->assertInstanceOf('TinCan\Group', $obj->getTarget()); 50 | } 51 | 52 | public function testSetTargetAsSubStatement() { 53 | $obj = new StubStatementBase(); 54 | $ss = [ 55 | 'objectType' => 'SubStatement' 56 | ]; 57 | $obj->setTarget($ss); 58 | $this->assertInstanceOf('TinCan\SubStatement', $obj->getTarget()); 59 | } 60 | 61 | public function testSetTargetInvalidArgumentException() { 62 | $badObjectType = 'imABadObjectType'; 63 | $this->setExpectedException( 64 | "InvalidArgumentException", 65 | "arg1 must implement the StatementTargetInterface objectType not recognized:$badObjectType" 66 | ); 67 | $obj = new StubStatementBase(); 68 | $ss = [ 69 | 'objectType' => $badObjectType 70 | ]; 71 | $obj->setTarget($ss); 72 | } 73 | 74 | public function testSetActorAsGroup() { 75 | $obj = new StubStatementBase(); 76 | $ss = [ 77 | 'objectType' => 'Group' 78 | ]; 79 | $obj->setActor($ss); 80 | $this->assertInstanceOf('TinCan\Group', $obj->getActor()); 81 | } 82 | 83 | public function testSetTimestampInvalidArgumentException() { 84 | $this->setExpectedException( 85 | "InvalidArgumentException", 86 | 'type of arg1 must be string or DateTime' 87 | ); 88 | 89 | $obj = new StubStatementBase(); 90 | $obj->setTimestamp(1); 91 | } 92 | 93 | /** 94 | * @dataProvider statementPropertyValueProvider 95 | */ 96 | public function testCompareWithSignaturePropertyMissing($property, $value) { 97 | $signature = new \stdClass; 98 | $setMethodName = 'set' . ucfirst($property); 99 | 100 | $obj = new StubStatementBase(); 101 | $obj->$setMethodName($value); 102 | 103 | $result = $obj->compareWithSignature($signature); 104 | $this->assertFalse($result['success']); 105 | $this->assertEquals("Comparison of $property failed: value not in signature", $result['reason']); 106 | 107 | $obj = new StubStatementBase(); 108 | $signature->$property = $value; 109 | 110 | $result = $obj->compareWithSignature($signature); 111 | $this->assertFalse($result['success']); 112 | $this->assertEquals("Comparison of $property failed: value not in this", $result['reason']); 113 | } 114 | 115 | public function statementPropertyValueProvider() { 116 | return [ 117 | ['actor', new Agent()], 118 | ['verb', new Verb()], 119 | ['target', new Agent()], 120 | ['context', new Context()], 121 | ['result', new Result()], 122 | ]; 123 | } 124 | 125 | public function testCompareWithSignatureTimestampMissing() { 126 | $timestamp = "2004-02-12T15:19:21+00:00"; 127 | $signature = new \stdClass; 128 | 129 | $obj = new StubStatementBase(); 130 | $obj->setTimestamp($timestamp); 131 | 132 | $result = $obj->compareWithSignature($signature); 133 | 134 | $this->assertFalse($result['success']); 135 | $this->assertEquals("Comparison of timestamp failed: value not in signature", $result['reason']); 136 | 137 | $obj = new StubStatementBase(); 138 | $signature->timestamp = $timestamp; 139 | 140 | $result = $obj->compareWithSignature($signature); 141 | 142 | $this->assertFalse($result['success']); 143 | $this->assertEquals("Comparison of timestamp failed: value not in this", $result['reason']); 144 | } 145 | 146 | public function testCompareWithSignatureTimestampNotEqual() { 147 | $timestamp = "2004-02-12T15:19:21+00:00"; 148 | $signature = new \stdClass; 149 | 150 | $obj = new StubStatementBase(); 151 | $obj->setTimestamp($timestamp); 152 | $signature->timestamp = "2005-02-12T15:19:21+00:00"; 153 | 154 | $result = $obj->compareWithSignature($signature); 155 | 156 | $this->assertFalse($result['success']); 157 | $this->assertEquals("Comparison of timestamp failed: value is not the same", $result['reason']); 158 | 159 | //Now check with microseconds. 160 | $timestamp = "2012-07-08 11:14:15.638276"; 161 | $obj = new StubStatementBase(); 162 | $obj->setTimestamp($timestamp); 163 | $signature->timestamp = "2012-07-08 11:14:15.638286"; 164 | 165 | $dt = new \DateTime($timestamp); 166 | $dt2 = new \DateTime($signature->timestamp); 167 | 168 | $result = $obj->compareWithSignature($signature); 169 | 170 | $this->assertFalse($result['success']); 171 | $this->assertEquals("Comparison of timestamp failed: value is not the same", $result['reason']); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tests/StatementRefTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('TinCan\StatementRef', $obj); 27 | $this->assertAttributeEmpty('id', $obj, 'id empty'); 28 | $this->assertAttributeNotEmpty('objectType', $obj, 'objectType not empty'); 29 | } 30 | 31 | public function testGetObjectType() { 32 | $obj = new StatementRef(); 33 | $this->assertSame('StatementRef', $obj->getObjectType()); 34 | } 35 | 36 | public function testId() { 37 | $obj = new StatementRef(); 38 | $id = Util::getUUID(); 39 | $this->assertSame($obj, $obj->setId($id)); 40 | $this->assertSame($id, $obj->getId()); 41 | } 42 | 43 | public function testSetIdThrowsException() { 44 | $this->setExpectedException( 45 | 'InvalidArgumentException', 46 | 'arg1 must be a UUID' 47 | ); 48 | $obj = new StatementRef(['id' => 'foo']); 49 | } 50 | 51 | // TODO: need to loop versions 52 | public function testAsVersion() { 53 | $args = [ 54 | 'id' => Util::getUUID(), 55 | ]; 56 | 57 | $obj = StatementRef::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 58 | $versioned = $obj->asVersion('1.0.0'); 59 | 60 | $args['objectType'] = 'StatementRef'; 61 | 62 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 63 | } 64 | 65 | public function testAsVersionEmpty() { 66 | $args = []; 67 | 68 | $obj = StatementRef::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 69 | $versioned = $obj->asVersion('1.0.0'); 70 | 71 | $args['objectType'] = 'StatementRef'; 72 | 73 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 74 | } 75 | 76 | public function testCompareWithSignature() { 77 | $success = ['success' => true, 'reason' => null]; 78 | $failure = ['success' => false, 'reason' => null]; 79 | 80 | $id = Util::getUUID(); 81 | $obj = new StatementRef(['id' => $id]); 82 | $sig = new StatementRef(['id' => $id]); 83 | 84 | $this->assertSame($success, $obj->compareWithSignature($sig), 'id only: match'); 85 | 86 | $sig->setId(Util::getUUID()); 87 | $failure['reason'] = 'Comparison of id failed: value is not the same'; 88 | 89 | $this->assertSame($failure, $obj->compareWithSignature($sig), 'id only: mismatch'); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/StatementVariationsTest.php: -------------------------------------------------------------------------------- 1 | [ 40 | 'mbox' => COMMON_MBOX 41 | ], 42 | 'verb' => [ 43 | 'id' => COMMON_VERB_ID 44 | ], 45 | 'object' => new Activity([ 46 | 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Basic' 47 | ]) 48 | ] 49 | ); 50 | 51 | foreach (self::$lrss as $lrs) { 52 | $response = $lrs->saveStatement($statement); 53 | $this->assertInstanceOf('TinCan\LRSResponse', $response); 54 | $this->assertTrue($response->success, "successful request"); 55 | } 56 | } 57 | 58 | public function testAttachmentsMetaOnly() { 59 | $text_content = "Content created at: " . Util::getTimestamp(); 60 | 61 | $statement = new Statement( 62 | [ 63 | 'actor' => [ 64 | 'mbox' => COMMON_MBOX 65 | ], 66 | 'verb' => [ 67 | 'id' => COMMON_VERB_ID 68 | ], 69 | 'object' => new Activity([ 70 | 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Basic' 71 | ]), 72 | 'attachments' => [ 73 | [ 74 | 'usageType' => 'http://id.tincanapi.com/attachment/supporting_media', 75 | 'display' => ['en-US' => 'StatementVariantsTest::testAttachmentsMetaOnly'], 76 | 'contentType' => 'text/plain; charset=ascii', 77 | 'length' => 25, 78 | 'sha2' => hash('sha256', $text_content), 79 | 'fileUrl' => 'http://tincanapi.com/TinCanPHP/Test/AttachmentFileUrl' 80 | ] 81 | ] 82 | ] 83 | ); 84 | 85 | foreach (self::$lrss as $lrs) { 86 | $response = $lrs->saveStatement($statement); 87 | $this->assertInstanceOf('TinCan\LRSResponse', $response); 88 | $this->assertTrue($response->success, "successful request"); 89 | } 90 | } 91 | 92 | public function testAttachmentsString() { 93 | $text_content = "Content created at: " . Util::getTimestamp(); 94 | 95 | $statement = new Statement( 96 | [ 97 | 'actor' => [ 98 | 'mbox' => COMMON_MBOX 99 | ], 100 | 'verb' => [ 101 | 'id' => COMMON_VERB_ID 102 | ], 103 | 'object' => new Activity([ 104 | 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/AttachmentsString' 105 | ]), 106 | 'attachments' => [ 107 | [ 108 | 'usageType' => 'http://id.tincanapi.com/attachment/supporting_media', 109 | 'display' => ['en-US' => 'StatementVariantsTest::testAttachmentsString'], 110 | 'contentType' => 'text/plain; charset=ascii', 111 | 'content' => $text_content 112 | ] 113 | ] 114 | ] 115 | ); 116 | 117 | foreach (self::$lrss as $lrs) { 118 | $response = $lrs->saveStatement($statement); 119 | $this->assertInstanceOf('TinCan\LRSResponse', $response); 120 | $this->assertTrue($response->success, "successful request"); 121 | } 122 | } 123 | 124 | public function testAttachmentsBinaryFile() { 125 | $file = 'tests/files/image.jpg'; 126 | 127 | $statement = new Statement( 128 | [ 129 | 'actor' => [ 130 | 'mbox' => COMMON_MBOX 131 | ], 132 | 'verb' => [ 133 | 'id' => COMMON_VERB_ID 134 | ], 135 | 'object' => new Activity([ 136 | 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/AttachmentsBinary' 137 | ]), 138 | 'attachments' => [ 139 | [ 140 | 'usageType' => 'http://id.tincanapi.com/attachment/supporting_media', 141 | 'display' => ['en-US' => 'StatementVariantsTest::testAttachmentsString'], 142 | 'contentType' => 'text/plain; charset=ascii', 143 | 'content' => file_get_contents($file) 144 | ] 145 | ] 146 | ] 147 | ); 148 | 149 | foreach (self::$lrss as $lrs) { 150 | $saveResponse = $lrs->saveStatement($statement); 151 | $this->assertInstanceOf('TinCan\LRSResponse', $saveResponse); 152 | $this->assertTrue($saveResponse->success, "successful request"); 153 | 154 | $retrieveResponse = $lrs->retrieveStatement($saveResponse->content->getId(), ['attachments' => true]); 155 | $this->assertInstanceOf('TinCan\LRSResponse', $retrieveResponse); 156 | $this->assertTrue($retrieveResponse->success); 157 | $this->assertInstanceOf('TinCan\Statement', $retrieveResponse->content); 158 | 159 | $this->assertSame($retrieveResponse->content->getAttachments()[0]->getSha2(), hash('sha256', file_get_contents($file)), 'verify content'); 160 | } 161 | } 162 | 163 | public function testSignedAndVerified() { 164 | $statement = new Statement( 165 | [ 166 | 'actor' => [ 167 | 'mbox' => COMMON_MBOX 168 | ], 169 | 'verb' => [ 170 | 'id' => COMMON_VERB_ID 171 | ], 172 | 'object' => new Activity([ 173 | 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Signed' 174 | ]) 175 | ] 176 | ); 177 | $statement->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password']); 178 | 179 | foreach (self::$lrss as $lrs) { 180 | $saveResponse = $lrs->saveStatement($statement); 181 | $this->assertInstanceOf('TinCan\LRSResponse', $saveResponse); 182 | $this->assertTrue($saveResponse->success, "successful request"); 183 | 184 | $retrieveResponse = $lrs->retrieveStatement($saveResponse->content->getId(), ['attachments' => true]); 185 | $this->assertInstanceOf('TinCan\LRSResponse', $retrieveResponse); 186 | $this->assertTrue($retrieveResponse->success); 187 | $this->assertInstanceOf('TinCan\Statement', $retrieveResponse->content); 188 | 189 | $verificationResult = $retrieveResponse->content->verify(['publicKey' => 'file://' . $GLOBALS['KEYs']['public']]); 190 | $this->assertTrue($verificationResult['success'], 'verify signature'); 191 | } 192 | } 193 | 194 | public function testSignedAndVerifiedX5c() { 195 | $statement = new Statement( 196 | [ 197 | 'actor' => [ 198 | 'mbox' => COMMON_MBOX 199 | ], 200 | 'verb' => [ 201 | 'id' => COMMON_VERB_ID 202 | ], 203 | 'object' => new Activity([ 204 | 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Signed' 205 | ]) 206 | ] 207 | ); 208 | $statement->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'], ['x5c' => 'file://' . $GLOBALS['KEYs']['public']]); 209 | 210 | foreach (self::$lrss as $lrs) { 211 | $saveResponse = $lrs->saveStatement($statement); 212 | $this->assertInstanceOf('TinCan\LRSResponse', $saveResponse); 213 | if (! $saveResponse->success) { 214 | print_r($saveResponse); 215 | } 216 | $this->assertTrue($saveResponse->success, "successful request"); 217 | 218 | $retrieveResponse = $lrs->retrieveStatement($saveResponse->content->getId(), ['attachments' => true]); 219 | $this->assertInstanceOf('TinCan\LRSResponse', $retrieveResponse); 220 | $this->assertTrue($retrieveResponse->success); 221 | $this->assertInstanceOf('TinCan\Statement', $retrieveResponse->content); 222 | 223 | $verificationResult = $retrieveResponse->content->verify(); 224 | $this->assertTrue($verificationResult['success'], 'verify signature'); 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /tests/TestCompareWithSignatureTrait.php: -------------------------------------------------------------------------------- 1 | true, 'reason' => null]; 24 | 25 | for ($i = 0; $i < count($cases); $i++) { 26 | $obj = new $class($cases[$i]['objArgs']); 27 | $sig = new $class(isset($cases[$i]['sigArgs']) ? $cases[$i]['sigArgs'] : $cases[$i]['objArgs']); 28 | if (isset($cases[$i]['reason'])) { 29 | $failure = ['success' => false, 'reason' => $cases[$i]['reason']]; 30 | } 31 | 32 | $this->assertSame(isset($cases[$i]['reason']) ? $failure : $success, $obj->compareWithSignature($sig), $cases[$i]['description']); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/UtilTest.php: -------------------------------------------------------------------------------- 1 | assertRegExp(Util::UUID_REGEX, $result); 27 | } 28 | 29 | public function testGetTimestamp() { 30 | $result = Util::getTimestamp(); 31 | 32 | // 33 | // this isn't intended to match all ISO8601 just *our* format of it, so it should 34 | // catch regressions, at least more than will be accepted by an LRS which is really 35 | // ultimately what we want in our tests 36 | // 37 | $this->assertRegExp('/\d\d\d\d-[01]\d-[0123]\dT[012]\d:[012345]\d:[012345]\d\.\d\d\d\+00:00/', $result); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/VerbTest.php: -------------------------------------------------------------------------------- 1 | 'experienced', 30 | 'en-GB' => 'experienced', 31 | 'es' => 'experimentado', 32 | 'fr' => 'expérimenté', 33 | 'it' => 'esperto' 34 | ]; 35 | } 36 | 37 | public function testInstantiation() { 38 | $obj = new Verb(); 39 | $this->assertInstanceOf('TinCan\Verb', $obj); 40 | $this->assertAttributeEmpty('id', $obj, 'id empty'); 41 | $this->assertAttributeInstanceOf('TinCan\LanguageMap', 'display', $obj, 'display is LanguageMap'); 42 | } 43 | 44 | public function testFromJSONInvalidNull() { 45 | $this->setExpectedException('TinCan\JSONParseErrorException'); 46 | $obj = Verb::fromJSON(null); 47 | } 48 | 49 | public function testFromJSONInvalidEmptyString() { 50 | $this->setExpectedException('TinCan\JSONParseErrorException'); 51 | $obj = Verb::fromJSON(''); 52 | } 53 | 54 | public function testFromJSONInvalidMalformed() { 55 | $this->setExpectedException('TinCan\JSONParseErrorException'); 56 | $obj = Verb::fromJSON('{id:"some value"}'); 57 | } 58 | 59 | public function testFromJSONIDOnly() { 60 | $obj = Verb::fromJSON('{"id":"' . COMMON_VERB_ID . '"}'); 61 | $this->assertInstanceOf('TinCan\Verb', $obj); 62 | $this->assertAttributeEquals(COMMON_VERB_ID, 'id', $obj, 'id matches'); 63 | $this->assertTrue($obj->getDisplay()->isEmpty(), 'display empty'); 64 | } 65 | 66 | // TODO: need to loop versions 67 | public function testAsVersion() { 68 | $args = [ 69 | 'id' => COMMON_VERB_ID, 70 | 'display' => self::$DISPLAY 71 | ]; 72 | 73 | $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 74 | $versioned = $obj->asVersion('1.0.0'); 75 | 76 | $this->assertEquals($versioned, $args, "serialized version matches original"); 77 | } 78 | 79 | public function testAsVersionEmpty() { 80 | $args = []; 81 | 82 | $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 83 | $versioned = $obj->asVersion('1.0.0'); 84 | 85 | $this->assertEquals($versioned, $args, "serialized version matches original"); 86 | } 87 | 88 | public function testAsVersionEmptyLanguageMap() { 89 | $args = ['display' => []]; 90 | 91 | $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 92 | $versioned = $obj->asVersion('1.0.0'); 93 | 94 | unset($args['display']); 95 | 96 | $this->assertEquals($versioned, $args, "serialized version matches corrected"); 97 | } 98 | 99 | public function testAsVersionEmptyStringInLanguageMap() { 100 | $args = ['display' => ['en' => '']]; 101 | 102 | $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); 103 | $versioned = $obj->asVersion('1.0.0'); 104 | 105 | $this->assertEquals($versioned, $args, "serialized version matches original"); 106 | } 107 | 108 | public function testCompareWithSignature() { 109 | $full = [ 110 | 'id' => COMMON_VERB_ID, 111 | 'display' => self::$DISPLAY 112 | ]; 113 | $display2 = array_replace(self::$DISPLAY, ['en-US' => 'not experienced']); 114 | $cases = [ 115 | [ 116 | 'description' => 'all null', 117 | 'objArgs' => [] 118 | ], 119 | [ 120 | 'description' => 'id', 121 | 'objArgs' => ['id' => COMMON_VERB_ID] 122 | ], 123 | [ 124 | 'description' => 'display', 125 | 'objArgs' => ['display' => self::$DISPLAY] 126 | ], 127 | [ 128 | 'description' => 'all', 129 | 'objArgs' => $full 130 | ], 131 | 132 | // 133 | // display is not matched for signature purposes because it 134 | // is not supposed to affect the meaning of the statement 135 | // 136 | [ 137 | 'description' => 'display only: mismatch (allowed)', 138 | 'objArgs' => ['display' => self::$DISPLAY ], 139 | 'sigArgs' => ['display' => $display2 ] 140 | ], 141 | [ 142 | 'description' => 'full: display mismatch (allowed)', 143 | 'objArgs' => $full, 144 | 'sigArgs' => array_replace($full, ['display' => $display2 ]) 145 | ], 146 | 147 | [ 148 | 'description' => 'id only: mismatch', 149 | 'objArgs' => ['id' => COMMON_VERB_ID ], 150 | 'sigArgs' => ['id' => COMMON_VERB_ID . '/invalid' ], 151 | 'reason' => 'Comparison of id failed: value is not the same' 152 | ], 153 | [ 154 | 'description' => 'full: id mismatch', 155 | 'objArgs' => $full, 156 | 'sigArgs' => array_replace($full, ['id' => COMMON_VERB_ID . '/invalid']), 157 | 'reason' => 'Comparison of id failed: value is not the same' 158 | ] 159 | ]; 160 | $this->runSignatureCases("TinCan\Verb", $cases); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /tests/VersionTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf("TinCan\Version", Version::v101(), "factory returns instance"); 25 | } 26 | 27 | public function testToString() { 28 | $this->assertInternalType("string", (string) Version::v101(), "object converts to string"); 29 | } 30 | 31 | public function testHasValueReturnsBool() { 32 | $this->assertTrue(Version::v101()->hasValue(Version::V101), "object has correct value"); 33 | } 34 | 35 | public function testHasAnyValueReturnsBool() { 36 | $this->assertFalse(Version::v101()->hasAnyValue([Version::V100, Version::V095]), "object does not have values"); 37 | } 38 | 39 | public function testIsSupportedReturnsBool() { 40 | $this->assertTrue(Version::v100()->isSupported(), "1.0.0 should be supported"); 41 | $this->assertFalse(Version::v095()->isSupported(), "0.95 should not be supported"); 42 | } 43 | 44 | public function testIsLatestReturnsBool() { 45 | $this->assertTrue(Version::v103()->isLatest(), "1.0.3 should be the latest version"); 46 | $this->assertFalse(Version::v095()->isLatest(), "0.95 should not be the latest version"); 47 | } 48 | 49 | public function testSupported() { 50 | $result = Version::supported(); 51 | $this->assertNotContains(Version::V095, $result, "0.95 not included"); 52 | } 53 | 54 | public function testLatest() { 55 | $this->assertSame(Version::V103, Version::latest(), "match latest"); 56 | } 57 | 58 | public function testVersionFromString() { 59 | $number = '1.0.1'; 60 | $version = Version::fromString($number); 61 | 62 | $this->assertTrue($version->hasValue($number)); 63 | } 64 | 65 | public function testInvalidArgumentExceptionIsThrown() { 66 | $number = '1.8.01'; 67 | $this->setExpectedException( 68 | 'InvalidArgumentException', 69 | "Invalid version [$number]" 70 | ); 71 | $version = Version::fromString($number); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/config/bootstrap.php: -------------------------------------------------------------------------------- 1 | '', 6 | 'username' => '', 7 | 'password' => '', 8 | 'version' => '1.0.1' 9 | ] 10 | ]; 11 | $KEYs = [ 12 | 'public' => 'path/to/keys/cacert.pem', 13 | 'private' => 'path/to/keys/privkey.pem', 14 | 'password' => null 15 | ]; 16 | -------------------------------------------------------------------------------- /tests/config/config.travis-ci.php: -------------------------------------------------------------------------------- 1 | 'https://cloud.scorm.com/tc/0CKX3A0SF2/sandbox/', 6 | 'username' => 'PjRb2iE9WsUSso_UYCE', 7 | 'password' => '3qoocGjKnfoYrtJhPrU', 8 | 'version' => '1.0.1' 9 | ] 10 | ]; 11 | $KEYs = [ 12 | 'public' => getenv('TRAVIS_BUILD_DIR') . '/tests/keys/travis/cacert.pem', 13 | 'private' => getenv('TRAVIS_BUILD_DIR') . '/tests/keys/travis/privkey.pem', 14 | 'password' => 'travis' 15 | ]; 16 | -------------------------------------------------------------------------------- /tests/files/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RusticiSoftware/TinCanPHP/19acfc818fb5be97d23766d1e799bb2456be7b91/tests/files/image.jpg -------------------------------------------------------------------------------- /tests/keys/travis/cacert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEvTCCA6WgAwIBAgIJALmEAI75yP5hMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCVE4xETAPBgNVBAcTCEZyYW5rbGluMRkwFwYDVQQK 4 | ExBSdXN0aWNpIFNvZnR3YXJlMRIwEAYDVQQLEwlUaW5DYW5QSFAxFjAUBgNVBAMT 5 | DXRpbmNhbmFwaS5jb20xJDAiBgkqhkiG9w0BCQEWFXN1cHBvcnRAdGluY2FuYXBp 6 | LmNvbTAeFw0xNTAyMDQxNDMzMDNaFw0xODAyMDMxNDMzMDNaMIGaMQswCQYDVQQG 7 | EwJVUzELMAkGA1UECBMCVE4xETAPBgNVBAcTCEZyYW5rbGluMRkwFwYDVQQKExBS 8 | dXN0aWNpIFNvZnR3YXJlMRIwEAYDVQQLEwlUaW5DYW5QSFAxFjAUBgNVBAMTDXRp 9 | bmNhbmFwaS5jb20xJDAiBgkqhkiG9w0BCQEWFXN1cHBvcnRAdGluY2FuYXBpLmNv 10 | bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKpakp3azSEtxJX57839 11 | 8DNkSKGbNTuE63bgPMG1hKQaS6p7Af9I8snn7duHJDp+V4V91DSmH3N5fxcMID/F 12 | bc8JBTt/4Mhk2h2gAiu+9OeSmfKhrc+K2wMB2rUo31OccrGS/pUNKkHqmlIYemc7 13 | vABi5f3dHmDkg+ZB5SHFuG1N55HPRh0f4tbn00qSAY/ZBTtAhKQMMWgp9Tt4EmZJ 14 | Tdfi5k6c0wqSutMC96lbxfph+gv/pCx93wsebQuGS8VMisCqwC7C3xKLMI/2mzwj 15 | A24f46OwF3n+3/bcOTj4j83LwdXk0j2jolsu3b9YpTPgBFWPWFOeZWcKsqJ7wpGR 16 | f3kCAwEAAaOCAQIwgf8wHQYDVR0OBBYEFG8lyvCXz7f5xz7jXnfp0QoUEzFrMIHP 17 | BgNVHSMEgccwgcSAFG8lyvCXz7f5xz7jXnfp0QoUEzFroYGgpIGdMIGaMQswCQYD 18 | VQQGEwJVUzELMAkGA1UECBMCVE4xETAPBgNVBAcTCEZyYW5rbGluMRkwFwYDVQQK 19 | ExBSdXN0aWNpIFNvZnR3YXJlMRIwEAYDVQQLEwlUaW5DYW5QSFAxFjAUBgNVBAMT 20 | DXRpbmNhbmFwaS5jb20xJDAiBgkqhkiG9w0BCQEWFXN1cHBvcnRAdGluY2FuYXBp 21 | LmNvbYIJALmEAI75yP5hMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB 22 | AFBegmSHvACgabf7XyV0HkNbMdyLMmsXRtnxGhypWvduTNZGFD+GNeGZ6XQrJz5c 23 | Pr8yZQfkUxjS0FQ7Mv/3bkWO+A6yK5YWbLT6erEhsNN0cHLRFKxb2/CwJAoFYAZU 24 | Rx+lYh1kLLcN2yro/xEifDF2qXw4q8PtLg7lTKjiqbAj/Q8TBfQnjEDbqyYNJbI2 25 | fbu0aaX9TR/Z91m0sAIntEPNh+EGK3/Y10zTC6ThoIRjH18kS1ejYLLMZgU9PDi6 26 | VIh/hAoGbaDGQG2jxJ1W+VApjIm3WFEEvMudPhR75zIqo4xNvamo8GyZ2ymY+B+0 27 | NE1BsnAXcJQZbRBGxearNoI= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /tests/keys/travis/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-256-CBC,EB52A2AA887778D63A4F265E90EA1B78 4 | 5 | +Hsycyk95RGBVMVUv+N+smMTnjvGOyU5QrwkNRUPzpy+svTuTJkwwihVZYLwG2Uv 6 | VAUHjGjE1M4gOmHNH67xesHQkGAPfdinmkkOB042YAEThJAxoAXEoN1wvK6LWlrI 7 | jwosVbe29cnauBHxiwtEABuPiH2DrSdlcJdzospFDyGwNomYHIyDP/JagbDumIUd 8 | DiZ7gPKVlKy0lWq8WvxpWSrAG4IwuAcnGLOfD1IpLJYKkKAtgqO0r6xzIwFdneLC 9 | lqBQALArZd0/wRvbAZkO7SFD5DMeJuhqs2orCLsEkMB5eoE9fed9hbYg/U2epebJ 10 | e9s9oSkVndmYC07UaxKG7n2srGmWBCXOWHzleaa07yGg0/xv6ginuyXUnzN4nM0F 11 | Vib9rmYAG43WarR+6+kAEdRQLSqoIST43DMn6jyThbr2Cr/VujFiK4i8/zp+Tp7D 12 | KNlbM5CkbGsjYtdmgne527rKA7JlDo8ZoJLNltvNbt7HE6qaRe/sE31z/j3GX1wd 13 | 7FswZMlWYG+rFDOsBahl03wU6l1HUqid0TKEYl+r0WK0i+mBe8MtGR3y/3hskIAA 14 | vrXvU4cMAlqdn29D5KMK+6G4b7vazIVrj7qeOxuzmCYWLrUo1vdrvtm1TVPkN2ZG 15 | eG12Uw5aNHNv3iZU6Mn/iXt2M2V0CFotxwAhntDcewNHmm2Et7y3SZpGlS3wqsK3 16 | PbbbVjaXfYH6rl6AGO6+uQiesOb5cMhAF5eRF55MWrQZjxSTCZGoBbd0ou0aLhwL 17 | eNYJDj48ItN97SL0lBxik9KaSXS27HASp4Q3I26vKdq6YBkbLDCgvQ6in1MAzzZH 18 | NPkIcIz9kET7agEVOpWPWcaXBjIpjKGIkDNeFstUfCqqX3XEOP3Sg1pOJcE0fQ1p 19 | kLsetQWyRMnGHV52fzC8Qc5RTy0JIT/shBlL1qVoOEkUM5rhd7P+83L1jau2NRQX 20 | TstfYDkfmopbUQBqyVugOYaOEdqQ9A6/I8LrdG3aFj4cIzbfuk3dXZ0wApvgyiGT 21 | dlXvNw28IAD/rk3Yg6PzUHF3u2yB1bqwo+zcUpBDCcEOJbLFVhZF4wrScWjaIp/s 22 | vqb4bWcQQ/jE3eKeOt0FeQGYNemiJe130HeD0lwip6swl4Phs6LfwHcZ4vzqmIgT 23 | CZ36yE5ILFbqUzK/0kpfH1IOsdbaw98LPzdNlYNhWRcJ9fsykNUmnHEat0xrJzVs 24 | FJsMrXZ08H/3G/wwnbqxkPllmMPux4+pNTf4Qgdj7qrbwCGgtvg6iZ+AyHDUj17u 25 | 0wwHjX9s4oTDZ6hD+6J2vjxb3nIfZcC6Epm5LRKoyn7i6NT5Nsnsjo5BWCd7HAry 26 | QpRVkYutSQPAgw52qTq/wzerVDGlnaGvQmxS8CCnrpYUUSsNbqFxEakXDFqg3hYZ 27 | 32m3xOheZkpDr9BLp1StGvpxemBTePjcy0KfNGuF5nrI0wAcJTCuWX4dJQFR7/3L 28 | gTBU/jyoHz6hEVoFH/xgb/JHuSfFgupDJx8m/n+tln7uhbl3vjKjiRPHdWGOKzoP 29 | Fj4t+8myRdCRlzFVZhF/H4YbRpWg/0JK0h3pT/Vc9ta9rHFvkrEbjAY7m8xJxz3B 30 | -----END RSA PRIVATE KEY----- 31 | --------------------------------------------------------------------------------