├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── apigen.neon ├── composer.json ├── phpunit.xml.dist ├── src └── Jasny │ └── InviteCode.php └── tests ├── Jasny └── InviteCodeTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /composer.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | 8 | script: phpunit --coverage-text 9 | 10 | before_script: 11 | - composer install 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Arnold Daniels 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jasny Invite code 2 | ================= 3 | 4 | [![Build Status](https://secure.travis-ci.org/jasny/db.png?branch=master)](http://travis-ci.org/jasny/invite-code) 5 | 6 | This library can be used for requiring invitation codes at registration. This is often the case when an application is in private beta phase. 7 | 8 | ### Installation 9 | 10 | This library is registred at packagist as [jasny/invite-code](https://packagist.org/packages/jasny/invite-code) and can be easily installed using [composer](http://getcomposer.org/). 11 | 12 | composer require jasny/invite-code 13 | 14 | ### Generation 15 | 16 | To create 100 random invitation codes run the following on the command line 17 | 18 | ``` 19 | mkdir invite-codes 20 | cd invite-codes 21 | for i in {1..100}; do 22 | CODE=$(cat /dev/urandom | env LC_CTYPE=C tr -dc 'A-Z0-9' | fold -w 8 | head -n 1) 23 | touch $CODE 24 | echo $CODE 25 | done 26 | ``` 27 | 28 | ### Usage 29 | 30 | ```php 31 | Jasny\InviteCode::setDir('invite-codes'); 32 | 33 | $invite = new Jasny\InviteCode($_POST['invite']); 34 | 35 | if (!$invite->isValid()) { 36 | echo "Invalid invite code"; 37 | exit(); 38 | } 39 | 40 | if ($invite->isUsed()) { 41 | echo "Invite code is already used"; 42 | exit(); 43 | } 44 | 45 | $invite->useBy($_POST['name']); 46 | ``` 47 | -------------------------------------------------------------------------------- /apigen.neon: -------------------------------------------------------------------------------- 1 | # Source file or directory to parse 2 | source: src 3 | # Directory where to save the generated documentation 4 | destination: docs 5 | # List of allowed file extensions 6 | extensions: [php] 7 | # Mask to exclude file or directory from processing 8 | exclude: 9 | # Don't generate documentation for classes from file or directory with this mask 10 | skipDocPath: 11 | # Don't generate documentation for classes with this name prefix 12 | skipDocPrefix: 13 | # Character set of source files 14 | charset: auto 15 | # Main project name prefix 16 | main: Jasny\ 17 | 18 | # Title of generated documentation 19 | title: 'Jasny Invite code · API documentation' 20 | # Documentation base URL 21 | baseUrl: 22 | # Google Custom Search ID 23 | googleCseId: 24 | # Google Custom Search label 25 | googleCseLabel: 26 | # Google Analytics tracking code 27 | googleAnalytics: 28 | # Grouping of classes 29 | groups: auto 30 | # List of allowed HTML tags in documentation 31 | allowedHtml: [code] 32 | # Element types for search input autocomplete 33 | autocomplete: [classes, constants, functions] 34 | 35 | # Generate documentation for methods and properties with given access level 36 | accessLevels: [public] 37 | # Generate documentation for elements marked as internal and display internal documentation parts 38 | internal: No 39 | # Generate documentation for PHP internal classes 40 | php: No 41 | # Generate tree view of classes, interfaces and exceptions 42 | tree: Yes 43 | # Generate documentation for deprecated classes, methods, properties and constants 44 | deprecated: No 45 | # Generate documentation of tasks 46 | todo: No 47 | # Generate highlighted source code files 48 | sourceCode: Yes 49 | # Add a link to download documentation as a ZIP archive 50 | download: No 51 | # Wipe out the destination directory first 52 | wipeout: Yes 53 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasny/invite-code", 3 | "type": "library", 4 | "description": "Library for using invitation codes", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Arnold Daniels", 10 | "email": "arnold@jasny.net", 11 | "homepage": "http://www.jasny.net" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/jasny/invite-code/issues", 16 | "source": "https://github.com/jasny/invite-code" 17 | }, 18 | "require": { 19 | "php": ">=5.4.0" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "3.*", 23 | "mikey179/vfsstream": "1.*" 24 | }, 25 | "autoload": { 26 | "psr-0": { 27 | "Jasny\\InviteCode": "src/" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tests/ 7 | 8 | 9 | 10 | 11 | vendor 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Jasny/InviteCode.php: -------------------------------------------------------------------------------- 1 | code = $code; 49 | } 50 | 51 | /** 52 | * Get the filename for the invite code 53 | * 54 | * @return string 55 | */ 56 | protected function getFilename() 57 | { 58 | return rtrim(static::getDir(), '/') . '/' . $this->code; 59 | } 60 | 61 | /** 62 | * Check the invite code 63 | * 64 | * @return boolean 65 | */ 66 | public function isValid() 67 | { 68 | return file_exists($this->getFilename()); 69 | } 70 | 71 | /** 72 | * Check if the invite code has already been used 73 | * 74 | * @return boolean 75 | */ 76 | public function isUsed() 77 | { 78 | if (!file_exists($this->getFilename())) return false; 79 | 80 | return filesize($this->getFilename()) > 0; 81 | } 82 | 83 | /** 84 | * Mark as used 85 | * 86 | * @param string $user 87 | */ 88 | public function useBy($user) 89 | { 90 | if (!$this->isValid()) throw new \Exception("Invalid invitation code"); 91 | if ($this->isUsed()) throw new \Exception("Invitation has already been used"); 92 | 93 | file_put_contents($this->getFilename(), $user . "\n" . date('c')); 94 | } 95 | 96 | /** 97 | * Cast code to string 98 | * 99 | * @return string 100 | */ 101 | public function __toString() 102 | { 103 | return $this->code; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/Jasny/InviteCodeTest.php: -------------------------------------------------------------------------------- 1 | at($dir); 22 | vfsStream::newFile('USED')->at($dir)->setContent("user@example.com\n2014-12-01T02:15:00+00:00\n"); 23 | 24 | InviteCode::setDir(vfsStream::url('invite-codes')); 25 | } 26 | 27 | /** 28 | * Test InviteCode::getDir() 29 | */ 30 | public function testGetDir() 31 | { 32 | $this->assertEquals(vfsStream::url('invite-codes'), InviteCode::getDir()); 33 | } 34 | 35 | /** 36 | * Test InviteCode::isValid() 37 | */ 38 | public function testIsValid() 39 | { 40 | $unused = new InviteCode('UNUSED'); 41 | $used = new InviteCode('USED'); 42 | $invalid = new InviteCode('INVALID'); 43 | 44 | $this->assertTrue($unused->isValid()); 45 | $this->assertTrue($used->isValid()); 46 | $this->assertFalse($invalid->isValid()); 47 | } 48 | 49 | /** 50 | * Test InviteCode::isUsed() 51 | */ 52 | public function testIsUsed() 53 | { 54 | $unused = new InviteCode('UNUSED'); 55 | $used = new InviteCode('USED'); 56 | $invalid = new InviteCode('INVALID'); 57 | 58 | $this->assertFalse($unused->isUsed()); 59 | $this->assertTrue($used->isUsed()); 60 | $this->assertFalse($invalid->isUsed()); 61 | } 62 | 63 | /** 64 | * Test InviteCode::useBy() 65 | */ 66 | public function testUseBy() 67 | { 68 | $code = new InviteCode('UNUSED'); 69 | 70 | // Only using a partial date, but there is still a (minor) chance that the time stamp is changed 71 | $expect = "foo@bar.com\n" . date('Y-m-d\TH:'); 72 | 73 | $code->useBy("foo@bar.com"); 74 | 75 | $this->assertTrue(file_exists(vfsStream::url('invite-codes/UNUSED'))); 76 | 77 | $contents = file_get_contents(vfsStream::url('invite-codes/UNUSED')); 78 | $this->assertStringStartsWith($expect, $contents); 79 | } 80 | 81 | /** 82 | * Test InviteCode::useBy() with invalid code 83 | */ 84 | public function testUseBy_InvalidException() 85 | { 86 | $code = new InviteCode('INVALID'); 87 | 88 | $this->setExpectedException('Exception', "Invalid invitation code"); 89 | $code->useBy("foo@bar.com"); 90 | } 91 | 92 | /** 93 | * Test InviteCode::useBy() with used code 94 | */ 95 | public function testUseBy_UsedException() 96 | { 97 | $code = new InviteCode('USED'); 98 | 99 | $this->setExpectedException('Exception', "Invitation has already been used"); 100 | $code->useBy("foo@bar.com"); 101 | } 102 | 103 | /** 104 | * Test InviteCode::__toString() 105 | */ 106 | public function testToString() 107 | { 108 | $foobar = new InviteCode("foobar"); 109 | $this->assertEquals("foobar", (string)$foobar); 110 | 111 | $mixedcase = new InviteCode("MixedCase1234"); 112 | $this->assertEquals("MixedCase1234", (string)$mixedcase); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |