├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Doxyfile ├── README.md ├── build.xml ├── build ├── Doxyfile ├── phpdox.xml ├── phpmd.xml └── screenshots │ └── .keep ├── cache └── .keep ├── composer.json ├── db └── config │ ├── database.sample.yml │ └── database.testing.yml ├── examples ├── login.php └── runner.php ├── phprelease.ini ├── phpunit.xml ├── phpunit.xml.dist ├── src ├── Action.php ├── ActionDescriptor.php ├── ActionGenerator.php ├── ActionGeneratorTest.php ├── ActionLogger.php ├── ActionRequest.php ├── ActionRequestTest.php ├── ActionRunner.php ├── ActionRunnerTest.php ├── ActionTemplate │ ├── ActionTemplate.php │ ├── ActionTemplateTest.php │ ├── CodeGenActionTemplate.php │ ├── CodeGenActionTemplateTest.php │ ├── FileBasedActionTemplateTest.php │ ├── RecordActionTemplate.php │ ├── SampleActionTemplate.php │ ├── SampleActionTemplateTest.php │ ├── TwigActionTemplate.php │ ├── UpdateOrderingRecordActionTemplate.php │ └── UpdateOrderingRecordActionTemplateTest.php ├── ActionTest.php ├── ActionTrait │ └── RoleChecker.php ├── ColumnConvert.php ├── ColumnConvertTest.php ├── Csrf │ ├── CsrfArrayStorage.php │ ├── CsrfSessionStorage.php │ ├── CsrfStorage.php │ ├── CsrfToken.php │ ├── CsrfTokenProvider.php │ └── CsrfTokenProviderTest.php ├── EmailAction.php ├── Exception │ ├── ActionException.php │ ├── ActionNotFoundException.php │ ├── InvalidActionNameException.php │ ├── RequiredConfigKeyException.php │ ├── UnableToCreateActionException.php │ ├── UnableToWriteCacheException.php │ └── UndefinedTemplateException.php ├── FieldView │ ├── BootstrapFieldView.php │ ├── BootstrapFieldViewTest.php │ ├── DivFieldView.php │ └── DivFieldViewTest.php ├── GeneratedAction.php ├── Loggable.php ├── Messages │ ├── en.php │ └── zh_TW.php ├── MixinAction.php ├── Param │ ├── FileParam.php │ ├── Image │ │ ├── CropAndScaleResize.php │ │ ├── MaxHeightResize.php │ │ ├── MaxWidthResize.php │ │ └── ScaleResize.php │ ├── ImageParam.php │ ├── ImageParamTest.php │ ├── ImageResizer.php │ ├── Param.php │ └── ParamTest.php ├── RecordAction │ ├── BaseRecordAction.php │ ├── BulkCopyRecordAction.php │ ├── BulkDeleteRecordAction.php │ ├── BulkRecordAction.php │ ├── BulkZhConvertRecordAction.php │ ├── CreateRecordAction.php │ ├── DeleteRecordAction.php │ ├── UpdateOrderingRecordAction.php │ └── UpdateRecordAction.php ├── Result.php ├── ResultTest.php ├── ServiceContainer.php ├── Storage │ ├── FilePath.php │ ├── FilePathTest.php │ ├── FileRename │ │ └── Md5Rename.php │ ├── FileRenameMethods.php │ └── FileRenameMethodsTest.php ├── Template.php ├── Templates │ └── RecordAction.html.twig ├── Testing │ ├── ActionTestAssertions.php │ └── ActionTestCase.php ├── Utils.php ├── ValueType │ ├── BaseType.php │ ├── BoolType.php │ ├── BoolTypeTest.php │ ├── DateTimeType.php │ ├── DateTimeTypeTest.php │ ├── DirType.php │ ├── EmailType.php │ ├── FileType.php │ ├── IntType.php │ ├── IntTypeTest.php │ ├── IpType.php │ ├── IpTypeTest.php │ ├── Ipv4Type.php │ ├── Ipv6Type.php │ ├── JsonType.php │ ├── JsonTypeTest.php │ ├── NumType.php │ ├── PathType.php │ ├── RegexType.php │ ├── StrType.php │ ├── TimestampType.php │ ├── UrlType.php │ └── ValueTypeTest.php └── View │ ├── BaseView.php │ ├── ManyToManyCheckboxView.php │ ├── StackView.php │ ├── StackViewTest.php │ ├── TemplateView.php │ └── TemplateViewTest.php └── tests ├── ActionTrait └── RoleCheckerTest.php ├── ActionWithUserTest.php ├── EmailFieldActionTest.php ├── FooTemplateView.php ├── IntFieldActionTest.php ├── Model └── CRUDTest │ ├── FooUser.php │ ├── FooUserCollection.php │ └── FooUserCollectionBase.php ├── OrderBundle ├── Model │ ├── Order.php │ ├── OrderBase.php │ ├── OrderCollection.php │ ├── OrderCollectionBase.php │ ├── OrderItem.php │ ├── OrderItemBase.php │ ├── OrderItemCollection.php │ ├── OrderItemCollectionBase.php │ ├── OrderItemRepo.php │ ├── OrderItemSchema.php │ ├── OrderRepo.php │ └── OrderSchema.php └── Tests │ └── OrderItemTest.php ├── ProductBundle ├── Action │ ├── CreateProduct.php │ ├── CreateProductFile.php │ ├── CreateProductImage.php │ ├── ProductBaseMixin.php │ └── UpdateProduct.php ├── Model │ ├── Category.php │ ├── CategoryBase.php │ ├── CategoryCollection.php │ ├── CategoryCollectionBase.php │ ├── CategoryRepo.php │ ├── CategorySchema.php │ ├── Feature.php │ ├── FeatureBase.php │ ├── FeatureCollection.php │ ├── FeatureCollectionBase.php │ ├── FeatureRepo.php │ ├── FeatureSchema.php │ ├── Product.php │ ├── ProductBase.php │ ├── ProductCategory.php │ ├── ProductCategoryBase.php │ ├── ProductCategoryCollection.php │ ├── ProductCategoryCollectionBase.php │ ├── ProductCategoryRepo.php │ ├── ProductCategorySchema.php │ ├── ProductCollection.php │ ├── ProductCollectionBase.php │ ├── ProductFeature.php │ ├── ProductFeatureBase.php │ ├── ProductFeatureCollection.php │ ├── ProductFeatureCollectionBase.php │ ├── ProductFeatureRepo.php │ ├── ProductFeatureSchema.php │ ├── ProductFile.php │ ├── ProductFileBase.php │ ├── ProductFileCollection.php │ ├── ProductFileCollectionBase.php │ ├── ProductFileRepo.php │ ├── ProductFileSchema.php │ ├── ProductImage.php │ ├── ProductImageBase.php │ ├── ProductImageCollection.php │ ├── ProductImageCollectionBase.php │ ├── ProductImageRepo.php │ ├── ProductImageSchema.php │ ├── ProductLink.php │ ├── ProductLinkBase.php │ ├── ProductLinkCollection.php │ ├── ProductLinkCollectionBase.php │ ├── ProductLinkRepo.php │ ├── ProductLinkSchema.php │ ├── ProductProduct.php │ ├── ProductProductBase.php │ ├── ProductProductCollection.php │ ├── ProductProductCollectionBase.php │ ├── ProductProductRepo.php │ ├── ProductProductSchema.php │ ├── ProductProperty.php │ ├── ProductPropertyBase.php │ ├── ProductPropertyCollection.php │ ├── ProductPropertyCollectionBase.php │ ├── ProductPropertyRepo.php │ ├── ProductPropertySchema.php │ ├── ProductRecipe.php │ ├── ProductRecipeCollection.php │ ├── ProductRepo.php │ ├── ProductSchema.php │ ├── ProductSubsection.php │ ├── ProductSubsectionBase.php │ ├── ProductSubsectionCollection.php │ ├── ProductSubsectionCollectionBase.php │ ├── ProductSubsectionRepo.php │ ├── ProductSubsectionSchema.php │ ├── ProductType.php │ ├── ProductTypeBase.php │ ├── ProductTypeCollection.php │ ├── ProductTypeCollectionBase.php │ ├── ProductTypeRepo.php │ ├── ProductTypeSchema.php │ ├── ProductUseCase.php │ ├── ProductUseCaseCollection.php │ ├── Resource.php │ ├── ResourceBase.php │ ├── ResourceCollection.php │ ├── ResourceCollectionBase.php │ ├── ResourceRepo.php │ └── ResourceSchema.php ├── ProductActionTest.php └── Tests │ └── ProductTest.php ├── Templates └── foo.html ├── User └── Model │ ├── User.php │ ├── UserBase.php │ ├── UserCollection.php │ ├── UserCollectionBase.php │ ├── UserRepo.php │ └── UserSchema.php ├── bootstrap.php ├── config ├── database.yml ├── mysql.yml ├── mysql_configserver.yml ├── pgsql.yml ├── sqlite.yml └── tmp.yml ├── data └── 404.png ├── fixture ├── bulk_update_user4.php └── handle_with_result.json └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.rar 3 | *.tar 4 | *.tar.bz2 5 | *.tbz 6 | *.zip 7 | *.jar 8 | .sass-cache 9 | .lazy.yml 10 | .DS_Store 11 | .gitmodules 12 | .hg 13 | .svn 14 | cache/ 15 | db/config/*.php 16 | coverage/ 17 | .lazy.php 18 | vendor/ 19 | route 20 | .lazy.php 21 | cache/ 22 | config/application.yml 23 | config/database.yml 24 | config/framework.yml 25 | utils/Selenium/ 26 | cache.properties 27 | *SchemaProxy.php 28 | *CollectionBase.php 29 | *RepoBase.php 30 | *.swp 31 | /build/ 32 | .maghead-cli.yml 33 | .php_cs.cache 34 | composer.lock 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | # get mysql 5.6 3 | dist: trusty 4 | 5 | language: php 6 | php: 7 | # - 7.0 # trusty has some problem with phpunit 8 | - 7.1 9 | - hhvm 10 | env: 11 | - DB=sqlite 12 | - DB=mysql 13 | matrix: 14 | fast_finish: true 15 | allow_failures: 16 | - php: "hhvm" 17 | install: 18 | - travis_retry composer require satooshi/php-coveralls "^1" --no-update --dev --prefer-dist 19 | - travis_retry composer install 20 | before_script: 21 | - rm -rf *.sqlite 22 | - if [[ "$DB" == "pgsql" ]]; then vendor/maghead/maghead/travis/setup-pgsql ; fi 23 | - if [[ "$DB" == "mysql" ]]; then vendor/maghead/maghead/travis/setup-mysql ; fi 24 | - phpenv rehash 25 | - php vendor/bin/maghead use tests/config/sqlite.yml 26 | - php vendor/bin/maghead schema build -f 27 | script: 28 | - phpunit -c phpunit.xml.dist 29 | after_success: 30 | - php vendor/bin/coveralls -v 31 | cache: 32 | apt: true 33 | directories: 34 | - vendor 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOGS 2 | =================== 3 | 4 | Version x - Sun Nov 15 13:07:53 2015 5 | 6 | 1. CSRFTokenProvider now must be instancialized. 7 | 2. CSRF token verification is now enabled only when csrf token provider is given. 8 | 9 | Version 2.1 - Sat Aug 29 13:55:39 2015 10 | 11 | 1. Added `cache_dir` to ServiceContainer. 12 | 2. Provided an option for customizing field view class. 13 | 3. Added BootstrapFieldView class 14 | 15 | Version 2.0.0 - Tue Jun 30 14:23:00 2015 16 | 17 | 1. Improved action generator to use action template to generate action. 18 | 2. Added action templates. 19 | - File-based action template 20 | - Record action template 21 | - Update ordering record action template 22 | 2. Added ActionRunner:handleWith method to run action with $_Request directly. 23 | 3. Added CSRF token support. 24 | 4. Added service container. 25 | 5. Added image process. 26 | 6. Renamed SortRecordAction to UpdateOrderingRecordAction. 27 | 7. Refactored RecordAction options 28 | - Added options for 'request', 'parent', 'files' 29 | - Added ActionRequest for managing $_REQUEST, $_FILES parameters. 30 | 8. Raised test coverage to 70% 31 | 32 | ### Deprecation 33 | 34 | - ActionGenerator:generate2 35 | - ActionGenerator:generateRecordAction 36 | - ActionRunner:registerCRUD 37 | - ActionKit/View 38 | - ActionKit/CRUD 39 | 40 | 41 | Version 1.4 - Fri Apr 25 20:10:02 2014 42 | 43 | 1. Added SortRecordAction for sorting records. 44 | 2. Refactored BulkCopyAction with contentType attribute 45 | 3. Added contentType attribute support, currently for "ImageFile" and "File" 46 | 47 | Version 1.2 - Sat Dec 7 21:56:54 2013 48 | 49 | 1. Refactored ManyToManyCheckboxView to support hierarchical data. 50 | 2. Improved interface for StackView. 51 | 52 | -------------------------------------------------------------------------------- /build/phpdox.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /build/phpmd.xml: -------------------------------------------------------------------------------- 1 | 7 | Description of your coding standard 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build/screenshots/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corneltek/ActionKit/a85f04c48bdaeff4c30d35246be9545508911ada/build/screenshots/.keep -------------------------------------------------------------------------------- /cache/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corneltek/ActionKit/a85f04c48bdaeff4c30d35246be9545508911ada/cache/.keep -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corneltek/actionkit", 3 | "authors": [ 4 | { "name": "Yo-An Lin", "email": "yoanlin93+composer@gmail.com" } 5 | ], 6 | "require": { 7 | "php": ">=5.6", 8 | "corneltek/cascading-attribute": "^1", 9 | "corneltek/fileutil": "^1.7", 10 | "universal/universal": "2.0.x-dev", 11 | "corneltek/imagekit": "^1", 12 | "corneltek/codegen": "@dev", 13 | "corneltek/cliframework": "@dev", 14 | "corneltek/formkit": "^1", 15 | "phifty/locale": "^3", 16 | "twig/twig": "^2", 17 | "pekkis/mime-types": "^1", 18 | "pimple/pimple": "^3.0" 19 | }, 20 | "require-dev": { 21 | "maghead/maghead": "4.0.x-dev", 22 | "maghead/magsql": "@dev", 23 | "corneltek/webserver-runner": "dev-master", 24 | "corneltek/kendo": "4.0.x-dev" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "ActionKit\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "classmap": ["tests"], 33 | "psr-4": { 34 | "ProductBundle\\": "tests/ProductBundle/", 35 | "OrderBundle\\": "tests/OrderBundle/", 36 | "User\\": "tests/User/" 37 | } 38 | }, 39 | "extra": { "branch-alias": { "dev-master": "4.0.x-dev" } }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /db/config/database.sample.yml: -------------------------------------------------------------------------------- 1 | --- 2 | bootstrap: 3 | schema: 4 | auto_id: true 5 | paths: 6 | - tests 7 | data_source: 8 | default: sqlite 9 | nodes: 10 | mysql: 11 | dsn: "mysql:host=localhost;dbname=actionkit_test" 12 | # database: actionkit_test 13 | # user: root 14 | # pass: 15 | sqlite: 16 | dsn: "sqlite:tests.db" 17 | -------------------------------------------------------------------------------- /db/config/database.testing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | bootstrap: 3 | schema: 4 | auto_id: true 5 | paths: 6 | - tests 7 | data_source: 8 | default: sqlite 9 | nodes: 10 | mysql: 11 | dsn: "mysql:host=localhost;dbname=actionkit_test" 12 | # database: actionkit_test 13 | # user: root 14 | # pass: 15 | sqlite: 16 | dsn: "sqlite:tests.db" 17 | -------------------------------------------------------------------------------- /examples/login.php: -------------------------------------------------------------------------------- 1 | param('email')->renderAs('TextInput'); 15 | $this->param('password')->renderAs('PasswordInput'); 16 | } 17 | 18 | public function run() { 19 | 20 | if ( $this->arg('email') == 'test@test.com' && 21 | $this->arg('password') == 'test') { 22 | return $this->success('登入成功'); 23 | } else { 24 | if( $this->arg('email') != 'test@test.com') { 25 | return $this->error('無此帳號'); 26 | } else if($this->arg('password') != 'test') { 27 | return $this->error('密碼錯誤'); 28 | } 29 | } 30 | } 31 | } 32 | 33 | $container = new ActionKit\ServiceContainer; 34 | $runner = new ActionKit\ActionRunner($container); 35 | 36 | // you can also run action directly 37 | // $result = $runner->run('MyLoginAction',array( 'email' => '...', 'password' => '...' )); 38 | 39 | if (isset($_POST['action'])) { 40 | $sig = $_POST['action']; 41 | unset($_POST['action']); 42 | $result = $runner->run($sig, $_POST); 43 | //var_dump($result); 44 | echo $result->getMessage(); 45 | } else { 46 | $action = new MyLoginAction; 47 | echo $action->asView()->render(); // implies view class ActionKit\View\StackView 48 | } 49 | -------------------------------------------------------------------------------- /examples/runner.php: -------------------------------------------------------------------------------- 1 | handleWith(STDOUT, $_REQUEST)) { 8 | var_dump( $result ); 9 | } 10 | -------------------------------------------------------------------------------- /phprelease.ini: -------------------------------------------------------------------------------- 1 | ; Steps = PHPUnit, BumpVersion, GitCommit, GitTag, GitPush, GitPushTags 2 | Steps = PHPUnit, BumpVersion, GitCommit, GitTag, GitPush, GitPushTags 3 | ; VersionFrom = src/PHPRelease/Console.php 4 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | src 24 | 25 | 26 | 27 | tests 28 | 29 | 30 | 31 | tests/ProductBundle 32 | 33 | 34 | 35 | tests/OrderBundle 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | src 23 | 24 | src 25 | src/Cache 26 | src/Testing 27 | tests 28 | 29 | 30 | 31 | 32 | 33 | 34 | src 35 | 36 | 37 | 38 | tests 39 | 40 | 41 | 42 | tests/ProductBundle 43 | 44 | 45 | 46 | tests/OrderBundle 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/ActionDescriptor.php: -------------------------------------------------------------------------------- 1 | arguments = array_merge($this->parameters, array()); 28 | 29 | if (isset($this->arguments['__ajax_request'])) { 30 | unset($this->arguments['__ajax_request']); 31 | $this->ajax = true; 32 | } 33 | 34 | unset($this->arguments['__action']); 35 | unset($this->arguments['action']); 36 | 37 | // handle actionName 38 | $actionKey = isset($requestParameters['__action']) ? '__action' : 'action'; 39 | if (isset($requestParameters[$actionKey])) { 40 | $this->actionName = $requestParameters[$actionKey]; 41 | } 42 | } 43 | 44 | public function arg($field) 45 | { 46 | if (isset($this->arguments[$field])) { 47 | return $this->arguments[$field]; 48 | } 49 | return null; 50 | } 51 | 52 | 53 | 54 | 55 | /** 56 | * isInvalidActionName returns int 57 | * 58 | * @return integer matched count. 59 | */ 60 | public function isInvalidActionName() 61 | { 62 | return preg_match('/[^A-Za-z0-9:]/i', $this->actionName); 63 | } 64 | 65 | public function isFullQualifiedName() 66 | { 67 | return strpos($this->actionName, '::') !== false; 68 | } 69 | 70 | 71 | public function isAjax() 72 | { 73 | return $this->ajax; 74 | } 75 | 76 | public function getActionName() 77 | { 78 | return $this->actionName; 79 | } 80 | 81 | public function getArguments() 82 | { 83 | return $this->arguments; 84 | } 85 | 86 | public static function hasAction(array $requestParameters = array()) 87 | { 88 | return isset($requestParameters['__action']); 89 | } 90 | 91 | 92 | 93 | 94 | // XXX: the uploadedFile methods should be not used. 95 | /** 96 | * This is a simple uploaded file storage, it doesn't support multiple files 97 | */ 98 | public function uploadedFile($fieldName, $index = 0) 99 | { 100 | if (isset($this->uploadedFiles[$fieldName][$index])) { 101 | return $this->uploadedFiles[$fieldName][$index]; 102 | } 103 | } 104 | 105 | public function saveUploadedFile($fieldName, $index, $file) 106 | { 107 | return $this->uploadedFiles[$fieldName][$index] = $file; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/ActionRequestTest.php: -------------------------------------------------------------------------------- 1 | 'MyApp::Action::CreateUser', 13 | '__ajax_request' => true, 14 | 'account' => 'user@gmail.com', 15 | 'password' => md5('qwer1234'), 16 | )); 17 | $this->assertTrue($request->isAjax()); 18 | $this->assertEquals('MyApp::Action::CreateUser',$request->getActionName()); 19 | $this->assertSame([ 20 | 'account' => 'user@gmail.com', 21 | 'password' => '5d93ceb70e2bf5daa84ec3d0cd2c731a', 22 | ], $request->getArguments()); 23 | } 24 | 25 | public function testFullQualifiedName() 26 | { 27 | $request = new ActionRequest(array( 28 | '__action' => 'MyApp::Action::CreateUser', 29 | '__ajax_request' => true, 30 | 'account' => 'user@gmail.com', 31 | 'password' => md5('qwer1234'), 32 | )); 33 | $this->assertTrue($request->isFullQualifiedName()); 34 | } 35 | 36 | public function testNonFullQualifiedName() 37 | { 38 | $request = new ActionRequest(array( 39 | '__action' => 'CreateUser', 40 | '__ajax_request' => true, 41 | 'account' => 'user@gmail.com', 42 | 'password' => md5('qwer1234'), 43 | )); 44 | $this->assertFalse($request->isFullQualifiedName()); 45 | } 46 | 47 | public function testArg() 48 | { 49 | $request = new ActionRequest([ 50 | '__action' => 'MyApp::Action::CreateProduct', 51 | '__ajax_request' => true, 52 | 'account' => 'user@gmail.com', 53 | 'password' => md5('qwer1234'), 54 | ]); 55 | $this->assertEquals('user@gmail.com',$request->arg('account')); 56 | } 57 | 58 | public function testFiles() 59 | { 60 | $files = [ 61 | 'download' => [ 62 | 'name' => array( 63 | 'file1' => 'MyFile.txt', 64 | 'file2' => 'MyFile.jpg', 65 | ), 66 | 'type' => array( 67 | 'file1' => 'text/plain', 68 | 'file2' => 'image/jpeg', 69 | ), 70 | 'tmp_name' => array ( 71 | 'file1' => '/tmp/php/php1h4j1o', 72 | 'file2' => '/tmp/php/php6hst32', 73 | ), 74 | 'error' => array( 75 | 'file1' => UPLOAD_ERR_OK, 76 | 'file2' => UPLOAD_ERR_OK, 77 | ), 78 | 'size' => array( 79 | 'file1' => 123, 80 | 'file2' => 98174 81 | ), 82 | ], 83 | ]; 84 | $request = new ActionRequest([ 85 | '__action' => 'MyApp::Action::CreateProduct', 86 | '__ajax_request' => true, 87 | 'account' => 'user@gmail.com', 88 | 'password' => md5('qwer1234'), 89 | ], $files); 90 | $fileStash = $request->file('download'); 91 | $this->assertNotNull($fileStash); 92 | $this->assertCount(2, $fileStash); 93 | } 94 | 95 | public function testInvalidActionName() 96 | { 97 | $request = new ActionRequest(array( 98 | '__action' => 'bb_fjeijfe', 99 | '__ajax_request' => true, 100 | 'account' => 'user@gmail.com', 101 | 'password' => md5('qwer1234'), 102 | )); 103 | $this->assertEquals(1, $request->isInvalidActionName()); 104 | } 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/ActionTemplate/ActionTemplate.php: -------------------------------------------------------------------------------- 1 | 'test2', 17 | ] ], 18 | [ [ 19 | 'namespace' => 'test2', 20 | 'model' => 'test2Model', // model's name 21 | ] ], 22 | ]; 23 | } 24 | 25 | /** 26 | * @dataProvider failingArgumentProvider 27 | * @expectedException ActionKit\Exception\RequiredConfigKeyException 28 | */ 29 | public function testRecordActionTemplateFailingArguments($arguments) 30 | { 31 | $actionTemplate = new RecordActionTemplate(); 32 | $runner = new ActionRunner; 33 | $actionTemplate->register($runner, 'RecordActionTemplate', $arguments); 34 | } 35 | 36 | public function testRecordActionTemplate() 37 | { 38 | $actionTemplate = new RecordActionTemplate(); 39 | $runner = new ActionRunner; 40 | $actionTemplate->register($runner, 'RecordActionTemplate', array( 41 | 'namespace' => 'test2', 42 | 'model' => 'test2Model', // model's name 43 | 'types' => array( 44 | [ 'prefix' => 'Create'], 45 | [ 'prefix' => 'Update'], 46 | [ 'prefix' => 'Delete'], 47 | [ 'prefix' => 'BulkDelete'] 48 | ) 49 | )); 50 | 51 | $className = 'test2\Action\Updatetest2Model'; 52 | $this->assertCount(4, $runner->getPretreatments()); 53 | $this->assertNotNull($pretreatment = $runner->getActionPretreatment($className)); 54 | 55 | $generatedAction = $actionTemplate->generate($className, $pretreatment); 56 | $this->assertRequireGeneratedAction($className, $generatedAction); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/ActionTemplate/CodeGenActionTemplate.php: -------------------------------------------------------------------------------- 1 | register($runner, 'CodeGenActionTemplate', array( 16 | * 'namespace' => 'test2', 17 | * 'model' => 'test2Model', // model's name 18 | * 'types' => array('Create','Update','Delete','BulkDelete') 19 | * )); 20 | * 21 | * $className = 'test2\Action\UpdatetestModel'; 22 | * $generatedAction = $actionTemplate->generate($className, [ 23 | * 'extends' => "\\ActionKit\\RecordAction\\CreateRecordAction", 24 | * 'properties' => [ 25 | * 'recordClass' => "test2\\Model\\testModel", 26 | * ], 27 | * ]); 28 | * 29 | * $generatedAction->requireAt($cacheCodePath); 30 | * 31 | */ 32 | class CodeGenActionTemplate implements ActionTemplate 33 | { 34 | /** 35 | * @synopsis 36 | * 37 | * $template->register($runner, [ 38 | * 'action_class' => 'FooAction', 39 | * 'extends' => "\\ActionKit\\RecordAction\\{$type}RecordAction", 40 | * 'properties' => [ 41 | * 'recordClass' => $options['namespace'] . "\\Model\\" . $options['model'], 42 | * ], 43 | * ]); 44 | */ 45 | public function register(ActionRunner $runner, $asTemplate, array $options = array()) 46 | { 47 | if (isset($options['use'])) { 48 | array_unshift($options['use'], '\\ActionKit\\Action'); 49 | } else { 50 | $options['use'] = ['\\ActionKit\\Action']; 51 | } 52 | $runner->register($options['action_class'], $asTemplate, $options); 53 | } 54 | 55 | public function createActionClassFile($actionClass, array $options = array()) 56 | { 57 | $class = new ClassFile($actionClass); 58 | if (isset($options['use'])) { 59 | foreach ($options['use'] as $use) { 60 | $class->useClass($use); 61 | } 62 | } 63 | if (isset($options['extends'])) { 64 | $class->extendClass($options['extends']); 65 | } 66 | if (isset($options['properties'])) { 67 | foreach ($options['properties'] as $name => $value) { 68 | $class->addProperty($name, $value); 69 | } 70 | } 71 | if (isset($options['constants'])) { 72 | foreach ($options['constants'] as $name => $value) { 73 | $class->addConst($name, $value); 74 | } 75 | } 76 | if (isset($options['traits'])) { 77 | foreach ($options['traits'] as $traitClass) { 78 | $class->useTrait($traitClass); 79 | } 80 | } 81 | 82 | return $class; 83 | } 84 | 85 | public function generate($actionClass, array $options = array()) 86 | { 87 | $class = $this->createActionClassFile($actionClass, $options); 88 | $code = $class->render(); 89 | return new GeneratedAction($actionClass, $code, $class); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/ActionTemplate/CodeGenActionTemplateTest.php: -------------------------------------------------------------------------------- 1 | register($runner, 'CodeGenActionTemplate', array( 22 | 'action_class' => $className, 23 | 'use' => ['TestApp\Database'], 24 | 'extends' => 'Action', 25 | 'constants' => [ 26 | 'foo' => 123 27 | ], 28 | )); 29 | $this->assertCount(1, $runner->getPretreatments()); 30 | $this->assertNotNull($pretreatment = $runner->getActionPretreatment($className)); 31 | 32 | $generatedAction = $actionTemplate->generate($className, $pretreatment['arguments']); 33 | $this->assertRequireGeneratedAction($className, $generatedAction); 34 | } 35 | 36 | 37 | /** 38 | * @dataProvider classNameProvider 39 | */ 40 | public function testCodeGenTemplateActionSuccessfulGeneration($className) 41 | { 42 | $actionTemplate = new CodeGenActionTemplate(); 43 | $runner = new ActionRunner; 44 | 45 | $actionTemplate->register($runner, 'CodeGenActionTemplate', array( 46 | 'action_class' => $className, 47 | 'extends' => 'Action', 48 | )); 49 | $this->assertCount(1, $runner->getPretreatments()); 50 | $this->assertNotNull($pretreatment = $runner->getActionPretreatment($className)); 51 | 52 | $generatedAction = $actionTemplate->generate($className, $pretreatment['arguments']); 53 | $this->assertRequireGeneratedAction($className, $generatedAction); 54 | } 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/ActionTemplate/RecordActionTemplate.php: -------------------------------------------------------------------------------- 1 | register($runner, array( 16 | * 'namespace' => 'test', 17 | * 'model' => 'testModel', // model's name 18 | * 'allowed_roles' => array('admin', 'manager'), 19 | * 'types' => [ 20 | * ['prefix' => 'Create', 'allowed_roles' => ['user', 'admin'] ], 21 | * ['prefix' => 'Update'], 22 | * ['prefix' => 'Delete'] 23 | * ] 24 | * )); 25 | */ 26 | public function register(ActionRunner $runner, $asTemplate, array $options = array()) 27 | { 28 | if (isset($options['use'])) { 29 | array_unshift($options['use'], '\\ActionKit\\Action', '\\ActionKit\\RecordAction\\BaseRecordAction'); 30 | } else { 31 | $options['use'] = ['\\ActionKit\\Action', '\\ActionKit\\RecordAction\\BaseRecordAction']; 32 | } 33 | 34 | if (!isset($options['namespace'])) { 35 | throw new RequiredConfigKeyException('namespace', 'namespace of the generated action'); 36 | } 37 | if (!isset($options['model'])) { 38 | throw new RequiredConfigKeyException('model', 'required for creating record actions'); 39 | } 40 | if (! isset($options['types'])) { 41 | throw new RequiredConfigKeyException('types', 'types is an array of operation names for CRUD'); 42 | } 43 | 44 | foreach ((array) $options['types'] as $type) { 45 | // re-define type 46 | if (is_string($type)) { 47 | $type = [ 'prefix' => $type ]; 48 | } 49 | 50 | 51 | $actionClass = $options['namespace'] . '\\Action\\' . $type['prefix'] . $options['model']; 52 | $properties = ['recordClass' => $options['namespace'] . "\\Model\\" . $options['model']]; 53 | $traits = array(); 54 | if (isset($options['allowed_roles']) || isset($type['allowed_roles'])) { 55 | $properties['allowedRoles'] = isset($type['allowed_roles']) ? $type['allowed_roles'] : $options['allowed_roles']; 56 | $traits = ['ActionKit\\ActionTrait\\RoleChecker']; 57 | } 58 | $configs = [ 59 | 'extends' => "\\ActionKit\\RecordAction\\{$type['prefix']}RecordAction", 60 | 'properties' => $properties, 61 | 'traits' => $traits, 62 | 'use' => $options['use'] 63 | ]; 64 | 65 | $runner->register($actionClass, $asTemplate, $configs); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ActionTemplate/SampleActionTemplate.php: -------------------------------------------------------------------------------- 1 | generate('', array( 12 | * 'namespace' => 'Core', 13 | * 'action_name' => 'GrantAccess' 14 | * )); 15 | * 16 | * $generatedAction->requireAt($cacheFilePath); 17 | */ 18 | class SampleActionTemplate extends CodeGenActionTemplate 19 | { 20 | public function generate($actionClass, array $options = array()) 21 | { 22 | if (! isset($options['namespace'])) { 23 | throw new RequiredConfigKeyException('action_name'); 24 | } 25 | 26 | if (!isset($options['action_name'])) { 27 | throw new RequiredConfigKeyException('action_name'); 28 | } 29 | 30 | $namespace = $options['namespace']; 31 | $actionName = $options['action_name']; 32 | 33 | $actionClass = "$namespace\\Action\\$actionName"; 34 | $options = [ 'extends' => 'Action', ]; 35 | 36 | $class = $this->createActionClassFile($actionClass, $options); 37 | 38 | // General use statement 39 | $class->useClass('\\ActionKit\\Action'); 40 | $class->useClass('\\ActionKit\\RecordAction\\BaseRecordAction'); 41 | 42 | 43 | $class->addMethod('public', 'schema', [], ''); 44 | $class->addMethod('public', 'run', [], 'return $this->success("Success!");'); 45 | 46 | $code = $class->render(); 47 | return new GeneratedAction($actionClass, $code, $class); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ActionTemplate/SampleActionTemplateTest.php: -------------------------------------------------------------------------------- 1 | 'FooBar'] ], 17 | [ ['action_name' => 'CreateSample'] ], 18 | [ [] ] 19 | ]; 20 | } 21 | 22 | /** 23 | * @dataProvider failingArgumentProvider 24 | * @expectedException ActionKit\Exception\RequiredConfigKeyException 25 | */ 26 | public function testSampleActionTemplateWithException($arguments) 27 | { 28 | $generator = new ActionGenerator(); 29 | $generator->registerTemplate('SampleActionTemplate', new SampleActionTemplate()); 30 | $generator->generate('SampleActionTemplate', 'SampleAction', $arguments); 31 | } 32 | 33 | public function testSampleActionTemplate() 34 | { 35 | $generator = new ActionGenerator(); 36 | $generator->registerTemplate('SampleActionTemplate', new SampleActionTemplate()); 37 | $runner = new ActionRunner([ 'generator' => $generator ]); 38 | // $runner->registerAction('SampleActionTemplate', array('action_class' => 'SampleAction')); 39 | $action = $runner->getGenerator()->generate('SampleActionTemplate', 'SampleAction', [ 40 | 'namespace' => 'FooBar', 41 | 'action_name' => 'CreateSample' 42 | ]); 43 | $this->assertInstanceOf(GeneratedAction::class, $action); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/ActionTemplate/UpdateOrderingRecordActionTemplate.php: -------------------------------------------------------------------------------- 1 | register($runner, 'UpdateOrderingRecordActionTemplate', array( 13 | * 'namespace' => 'test2', 14 | * 'model' => 'Test2Model', // model's name 15 | * )); 16 | * 17 | * $className = 'test2\Action\SortTest2Model'; 18 | * $actionArgs = $runner->pretreatments[$className]['actionArgs']; 19 | * $generatedAction = $actionTemplate->generate($className, $actionArgs); 20 | * 21 | * $generatedAction->load(); 22 | */ 23 | class UpdateOrderingRecordActionTemplate extends RecordActionTemplate 24 | { 25 | public function register(ActionRunner $runner, $asTemplate, array $options = array()) 26 | { 27 | if (isset($options['use'])) { 28 | array_unshift($options['use'], '\\ActionKit\\Action', '\\ActionKit\\RecordAction\\BaseRecordAction'); 29 | } else { 30 | $options['use'] = ['\\ActionKit\\Action', '\\ActionKit\\RecordAction\\BaseRecordAction']; 31 | } 32 | 33 | 34 | if (!isset($options['model'])) { 35 | if (isset($options['record_class'])) { 36 | $nslist = explode("\\Model\\", $options['record_class']); 37 | $options['model'] = $nslist[1]; 38 | 39 | if (!isset($options['namespace'])) { 40 | $options['namespace'] = $nslist[0]; 41 | } 42 | } else { 43 | throw new RequiredConfigKeyException('model', 'required for creating record actions'); 44 | } 45 | } 46 | 47 | if (!isset($options['namespace'])) { 48 | throw new RequiredConfigKeyException('namespace', 'namespace'); 49 | } 50 | 51 | $actionClass = $options['namespace'] . '\\Action\\Update' . $options['model'] . 'Ordering'; 52 | $runner->register($actionClass, $asTemplate, [ 53 | 'extends' => "\\ActionKit\\RecordAction\\UpdateOrderingRecordAction", 54 | 'properties' => [ 55 | 'recordClass' => $options['namespace'] . "\\Model\\" . $options['model'], 56 | ] 57 | ]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/ActionTemplate/UpdateOrderingRecordActionTemplateTest.php: -------------------------------------------------------------------------------- 1 | ['OrderingTest\SomeProvider'] 17 | ] ], 18 | [ [ 19 | 'namespace' => 'OrderingTest', 20 | ] ], 21 | [ [ 22 | 'model' => 'Bar', // model's name 23 | ] ], 24 | ]; 25 | } 26 | 27 | 28 | /** 29 | * @dataProvider failingArgumentProvider 30 | * @expectedException ActionKit\Exception\RequiredConfigKeyException 31 | */ 32 | public function testUpdateOrderingRecordActionTemplateWithFailingArguments($arguments) 33 | { 34 | $recordClass = 'OrderingTest\Model\Foo'; 35 | $className = 'OrderingTest\Action\UpdateFooOrdering'; 36 | 37 | $actionTemplate = new UpdateOrderingRecordActionTemplate; 38 | $runner = new ActionRunner; 39 | $actionTemplate->register($runner, 'UpdateOrderingRecordActionTemplate', $arguments); 40 | } 41 | 42 | 43 | public function testGenerationWithRecordClassOption() 44 | { 45 | $recordClass = 'OrderingTest\Model\Foo'; 46 | $className = 'OrderingTest\Action\UpdateFooOrdering'; 47 | 48 | $actionTemplate = new UpdateOrderingRecordActionTemplate; 49 | $runner = new ActionRunner; 50 | $actionTemplate->register($runner, 'UpdateOrderingRecordActionTemplate', array( 51 | 'record_class' => $recordClass, 52 | )); 53 | $this->assertCount(1, $runner->getPretreatments()); 54 | $this->assertNotNull($pretreatment = $runner->getActionPretreatment($className)); 55 | $generatedAction = $actionTemplate->generate($className, $pretreatment); 56 | $this->assertRequireGeneratedAction($className, $generatedAction); 57 | } 58 | 59 | 60 | public function testUpdateOrderingRecordActionTemplate() 61 | { 62 | $actionTemplate = new UpdateOrderingRecordActionTemplate; 63 | $runner = new ActionRunner; 64 | $actionTemplate->register($runner, 'UpdateOrderingRecordActionTemplate', array( 65 | 'namespace' => 'OrderingTest', 66 | 'model' => 'Test2Model' // model's name 67 | )); 68 | 69 | $className = 'OrderingTest\Action\UpdateTest2ModelOrdering'; 70 | 71 | $this->assertCount(1, $runner->getPretreatments()); 72 | $this->assertNotNull($pretreatment = $runner->getActionPretreatment($className)); 73 | 74 | $generatedAction = $actionTemplate->generate($className, $pretreatment); 75 | $this->assertRequireGeneratedAction($className, $generatedAction); 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/ActionTrait/RoleChecker.php: -------------------------------------------------------------------------------- 1 | deny("Anonymous user is not allowed."); 13 | } 14 | 15 | if (is_string($user)) { 16 | if (in_array($user, $this->allowedRoles)) { 17 | return $this->allow(); 18 | } else { 19 | return $this->deny(); 20 | } 21 | } elseif ($user instanceof MultiRoleInterface || method_exists($user, 'getRoles')) { 22 | foreach ($user->getRoles() as $role) { 23 | if (in_array($role, $this->allowedRoles)) { 24 | return $this->allow(); 25 | } 26 | } 27 | return $this->deny(); 28 | } else { 29 | throw new Exception("Unsupported current user object"); 30 | } 31 | return $this->deny(); 32 | } 33 | 34 | public function allow($message = null) 35 | { 36 | return array(true, $message ?: $this->permissionAllowedMessage()); 37 | } 38 | 39 | public function deny($message = null) 40 | { 41 | return array(false, $message ?: $this->permissionDeniedMessage()); 42 | } 43 | 44 | public function getAllowedRoles() 45 | { 46 | return $this->allowedRoles; 47 | } 48 | 49 | public function permissionDeniedMessage() 50 | { 51 | return 'Permission denied.'; 52 | } 53 | 54 | public function permissionAllowedMessage() 55 | { 56 | return 'Permission allowed.'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ColumnConvertTest.php: -------------------------------------------------------------------------------- 1 | getColumn('created_at'); 40 | $this->assertInstanceOf(RuntimeColumn::class, $column); 41 | 42 | // assert the input 43 | $this->assertInstanceOf(Closure::class, $column->default); 44 | 45 | $param = ColumnConvert::toParam($column, new CreateOrder); 46 | $this->assertInstanceOf(Param::class, $param); 47 | $this->assertInstanceOf(DateTime::class, $param->getDefaultValue()); 48 | } 49 | 50 | public function testConvertColumnNotNull() 51 | { 52 | $schema = Order::getSchema(); 53 | $column = $schema->getColumn('amount'); 54 | $this->assertInstanceOf(RuntimeColumn::class, $column); 55 | 56 | $param = ColumnConvert::toParam($column, new CreateOrder); 57 | $this->assertInstanceOf(Param::class, $param); 58 | 59 | $this->assertTrue($param->required); 60 | } 61 | 62 | public function testConvertCurrentTimestampIntoPHPDateTimeObject() 63 | { 64 | $schema = Order::getSchema(); 65 | $column = $schema->getColumn('updated_at'); 66 | $this->assertInstanceOf(RuntimeColumn::class, $column); 67 | 68 | // assert the input 69 | $this->assertInstanceOf(Raw::class, $column->default); 70 | $this->assertEquals('CURRENT_TIMESTAMP', $column->default->__toString()); 71 | 72 | 73 | $param = ColumnConvert::toParam($column, new CreateOrder); 74 | $this->assertInstanceOf(Param::class, $param); 75 | $this->assertEquals('DateTime', $param->isa); 76 | $this->assertNull($param->getDefaultValue()); 77 | } 78 | 79 | public function testColumnConvert() 80 | { 81 | $schema = Order::getSchema(); 82 | $this->assertNotNull($schema); 83 | 84 | $order = new Order; 85 | $action = ColumnConvert::convertSchemaToAction($schema, $order); 86 | $this->assertNotNull($action); 87 | $this->assertInstanceOf(Action::class, $action); 88 | $this->assertInstanceOf(RecordAction\BaseRecordAction::class, $action); 89 | 90 | $view = $action->asView(View\StackView::class); 91 | $this->assertNotNull($view); 92 | $this->assertInstanceOf(View\StackView::class, $view); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Csrf/CsrfArrayStorage.php: -------------------------------------------------------------------------------- 1 | tokenKey = $key; 19 | $this->array = array(); 20 | } 21 | 22 | public function getTokenKey() 23 | { 24 | return $this->key; 25 | } 26 | 27 | public function store(CsrfToken $token, $key = null) 28 | { 29 | $this->array[$key ?: $this->tokenKey] = serialize($token); 30 | } 31 | 32 | public function exists($key = null) 33 | { 34 | return isset($this->array[$key ?: $this->tokenKey]); 35 | } 36 | 37 | /** 38 | * Load a CSRF token from session by specific session key 39 | * 40 | * @param string $key 41 | * @return CsrfToken 42 | */ 43 | public function load($key = null) 44 | { 45 | if (isset($this->array[$key ?: $this->tokenKey])) { 46 | return unserialize($this->array[$key ?: $this->tokenKey]); 47 | } 48 | } 49 | 50 | public function drop($key = null) 51 | { 52 | unset($this->array[$key ?: $this->tokenKey]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Csrf/CsrfSessionStorage.php: -------------------------------------------------------------------------------- 1 | sessionKey = $sessionKey; 17 | } 18 | 19 | public function getSessionKey() 20 | { 21 | return $this->sessionKey; 22 | } 23 | 24 | public function store(CsrfToken $token, $key = null) 25 | { 26 | $_SESSION[$key ?: $this->sessionKey] = serialize($token); 27 | } 28 | 29 | public function exists($key = null) 30 | { 31 | return isset($_SESSION[$key ?: $this->sessionKey]); 32 | } 33 | 34 | /** 35 | * Load a CSRF token from session by specific session key 36 | * 37 | * @param string $key 38 | * @return CsrfToken 39 | */ 40 | public function load($key = null) 41 | { 42 | if (isset($_SESSION[$key ?: $this->sessionKey])) { 43 | return unserialize($_SESSION[$key ?: $this->sessionKey]); 44 | } 45 | } 46 | 47 | public function drop($key = null) 48 | { 49 | unset($_SESSION[$key ?: $this->sessionKey]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Csrf/CsrfStorage.php: -------------------------------------------------------------------------------- 1 | id = $id; 41 | $this->ttl = $ttl; 42 | $this->timestamp = time(); // created_at 43 | $this->salt = $this->randomString(32); 44 | $this->extra = $extra; 45 | $this->hash = $this->generateChecksum(); 46 | } 47 | 48 | public function isExpired($now) 49 | { 50 | if ($this->ttl != 0) { 51 | return ($now - $this->timestamp) > $this->ttl; 52 | } 53 | return false; 54 | } 55 | 56 | public function toPublicArray() 57 | { 58 | return [ 59 | 'hash' => $this->hash, 60 | 'ttl' => $this->ttl, 61 | 'timestamp' => $this->timestamp, 62 | 'created_at' => date('c', $this->timestamp), 63 | 'expired_at' => date('c', $this->timestamp + $this->ttl), 64 | ]; 65 | } 66 | 67 | protected function randomString($len = 10) 68 | { 69 | $rString = ''; 70 | $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'; 71 | $charsTotal = strlen($chars); 72 | for ($i = 0; $i < $len; $i++) { 73 | $rInt = (integer) mt_rand(0, $charsTotal); 74 | $rString .= substr($chars, $rInt, 1); 75 | } 76 | return $rString; 77 | } 78 | 79 | /** 80 | * Output token hash 81 | * 82 | * @return string 83 | */ 84 | public function __toString() 85 | { 86 | return $this->hash; 87 | } 88 | 89 | /** 90 | * generateChecksum generates sha1 checksum and stored in base64 format string 91 | * 92 | * @return string 93 | */ 94 | protected function generateChecksum() 95 | { 96 | return base64_encode(sha1(serialize($this))); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Csrf/CsrfTokenProvider.php: -------------------------------------------------------------------------------- 1 | storage = $storage; 17 | } 18 | 19 | public function setTtl($seconds) 20 | { 21 | $this->ttl = $seconds; 22 | } 23 | 24 | /** 25 | * Generate a CSRF token and save the token into the session with the 26 | * current session key. 27 | * 28 | * @param integer $ttl time to live seconds 29 | * 30 | * @return CsrfToken 31 | */ 32 | public function generateToken() 33 | { 34 | $token = new CsrfToken(session_id(), $this->ttl, [ 35 | 'session_id' => session_id(), 36 | 'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0', 37 | ]); 38 | return $token; 39 | } 40 | 41 | public function loadCurrentToken($refresh = false) 42 | { 43 | if ($refresh) { 44 | $token = $this->generateToken(); 45 | $this->storage->store($token); 46 | } 47 | if ($token = $this->storage->load()) { 48 | return $token; 49 | } 50 | $token = $this->generateToken(); 51 | $this->storage->store($token); 52 | return $token; 53 | } 54 | 55 | public function getStorage() 56 | { 57 | return $this->storage; 58 | } 59 | 60 | /** 61 | * Verify incoming csrf token 62 | * 63 | * @param string $insecureTokenHash 64 | * @param integer $requestTime 65 | * @return boolean 66 | */ 67 | public function isValidToken($insecureTokenHash, $requestTime) 68 | { 69 | // Load the token object from the storage (session storage) 70 | $token = $this->storage->load(); 71 | if ($token) { 72 | if ($token->isExpired($requestTime)) { 73 | return false; 74 | } 75 | $generatedHash = $token->hash; 76 | if ($insecureTokenHash !== null && $generatedHash !== null) { 77 | return $insecureTokenHash === $generatedHash; 78 | } 79 | } 80 | return false; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Csrf/CsrfTokenProviderTest.php: -------------------------------------------------------------------------------- 1 | provider = new CsrfTokenProvider($storage); 17 | $this->provider->setTtl(1800); 18 | $this->token = $this->provider->loadCurrentToken(true); 19 | } 20 | 21 | public function testArrayStorage() 22 | { 23 | $storage = new CsrfArrayStorage('__csrf_token'); 24 | $provider = new CsrfTokenProvider($storage); 25 | $token = $provider->loadCurrentToken(); 26 | $this->assertTrue($storage->exists(), '__csrf_token should exists'); 27 | } 28 | 29 | public function testGenerateToken() 30 | { 31 | $token = $this->token; 32 | $this->assertNotNull($token); 33 | $this->assertEquals(1800, $token->ttl); 34 | $this->assertNotNull($token->hash); 35 | $this->assertNotNull($this->provider->loadCurrentToken()); 36 | } 37 | 38 | public function testVerifyToken() 39 | { 40 | $ret = $this->provider->isValidToken($this->token->hash, time()); 41 | $this->assertTrue($ret); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Exception/ActionException.php: -------------------------------------------------------------------------------- 1 | action = $action; 14 | parent::__construct($msg); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Exception/ActionNotFoundException.php: -------------------------------------------------------------------------------- 1 | actionClass = $actionClass; 13 | parent::__construct("Action class '$actionClass' not found, you might need to setup action autoloader or register the action template"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Exception/InvalidActionNameException.php: -------------------------------------------------------------------------------- 1 | configKey = $key; 20 | $this->configDesc = $desc; 21 | parent::__construct("Config '$key' is required. [$desc]"); 22 | } 23 | 24 | public function getConfigKey() 25 | { 26 | return $this->configKey; 27 | } 28 | 29 | public function getConfigDesc() 30 | { 31 | return $this->configDesc; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Exception/UnableToCreateActionException.php: -------------------------------------------------------------------------------- 1 | column = $column; 33 | 34 | if (isset($options['wrapperClass'])) { 35 | $this->wrapperClass = $options['wrapperClass']; 36 | } 37 | 38 | if (isset($options['labelClass'])) { 39 | $this->labelClass = $options['labelClass']; 40 | } 41 | 42 | if (isset($options['inputWrapperClass'])) { 43 | $this->inputWrapperClass = $options['inputWrapperClass']; 44 | } 45 | } 46 | 47 | public function setWidgetAttributes($attrs) 48 | { 49 | $this->widgetAttributes = $attrs; 50 | } 51 | 52 | /** 53 | * 54 | * Build a div structure like this: 55 | 56 |
57 | 58 |
59 | 60 |
61 |
62 | */ 63 | public function build() 64 | { 65 | $wrapper = new Div(array( 66 | 'class' => $this->wrapperClass, 67 | )); 68 | 69 | $widget = $this->column->createWidget(null, $this->widgetAttributes); 70 | $widget->addClass('form-control'); 71 | $widgetId = $widget->getSerialId(); 72 | 73 | $wrapper->addClass($widget->convertClassToCssClassName()); 74 | 75 | /* 76 | if ( $widget instanceof CheckboxInput ) { 77 | $label = $this->column->createLabelWidget(); 78 | $label->prepend($widget); 79 | $wrapper->append($label); 80 | */ 81 | if ($widget instanceof HiddenInput) { 82 | 83 | // $wrapper->append( $widget ); 84 | return $widget; 85 | } else { 86 | $inputDiv = new Div(array('class' => $this->inputWrapperClass)); 87 | $inputDiv->append($widget); 88 | 89 | $label = $this->column->createLabelWidget(); 90 | $label->setAttributes(array('class' => $this->labelClass, 'for' => $widgetId)); 91 | 92 | $wrapper->append($label); 93 | $wrapper->append($inputDiv); 94 | 95 | if ($this->column->hint) { 96 | $hintEl = new Span(array('class' => $this->hintClass )); 97 | $hintEl->append($this->column->hint); 98 | $inputDiv->append($hintEl); 99 | } 100 | } 101 | return $wrapper; 102 | } 103 | 104 | public function render() 105 | { 106 | return $this->build()->render(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/FieldView/BootstrapFieldViewTest.php: -------------------------------------------------------------------------------- 1 | required(1); 14 | 15 | $field = new BootstrapFieldView($column, array( 16 | 'labelClass' => 'col-lg-2', 17 | 'inputWrapperClass' => 'col-lg-10' 18 | )); 19 | $field->setWidgetAttributes(array( 20 | 'placeholder' => "ariel123", 21 | 'readoly' => "", 22 | 'autocomplete' => "off", 23 | )); 24 | $html = $field->render(); 25 | 26 | $xml = simplexml_load_string($html); 27 | 28 | $this->assertEquals('form-group formkit-widget-textinput', (string)$xml->attributes()['class']); 29 | $label = $xml->label; 30 | $this->assertEquals('col-lg-2', (string)$label->attributes()['class']); 31 | $this->assertEquals('* Account', (string)$label[0]); 32 | 33 | $div = $xml->div; 34 | $this->assertEquals('col-lg-10', (string)$div->attributes()['class']); 35 | $input = $div->input->attributes(); 36 | $this->assertEquals('formkit-widget formkit-widget-text form-control', $input->class); 37 | $this->assertEquals('account', $input->name); 38 | $this->assertEquals('ariel123', $input->placeholder); 39 | $this->assertEquals('off', $input->autocomplete); 40 | } 41 | } 42 | 43 | /* 44 | 45 |
46 | 47 |
48 | 49 |
50 |
51 | */ 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/FieldView/DivFieldView.php: -------------------------------------------------------------------------------- 1 | column = $column; 33 | } 34 | 35 | public function setWidgetAttributes($attrs) 36 | { 37 | $this->widgetAttributes = $attrs; 38 | } 39 | 40 | /** 41 | * 42 | * Build a div structure like this: 43 | 44 |
45 |
{% trans 'Role' %}
46 |
47 | 51 |
52 |
53 | */ 54 | public function build() 55 | { 56 | $wrapper = new Div(array( 57 | 'class' => $this->wrapperClass, 58 | )); 59 | 60 | 61 | $widget = $this->column->createWidget(null, $this->widgetAttributes); 62 | $wrapper->addClass($widget->convertClassToCssClassName()); 63 | 64 | if ($widget instanceof CheckboxInput) { 65 | $label = $this->column->createLabelWidget(); 66 | $label->prepend($widget); 67 | $wrapper->append($label); 68 | } elseif ($widget instanceof HiddenInput) { 69 | $wrapper->append($widget); 70 | } else { 71 | $labelDiv = new Div(array( 'class' => $this->labelClass )); 72 | $inputDiv = new Div(array( 'class' => $this->inputClass )); 73 | $inputDiv->append($widget); 74 | $label = $this->column->createLabelWidget(); 75 | $labelDiv->append($label); 76 | $wrapper->append($labelDiv); 77 | $wrapper->append($inputDiv); 78 | if ($this->column->hint) { 79 | $hintEl = new Span(array( 'class' => $this->hintClass )); 80 | $hintEl->append($this->column->hint); 81 | $wrapper->append($hintEl); 82 | } 83 | } 84 | return $wrapper; 85 | } 86 | 87 | public function render() 88 | { 89 | return $this->build()->render(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/FieldView/DivFieldViewTest.php: -------------------------------------------------------------------------------- 1 | required(1); 14 | $column->default('John'); 15 | 16 | $field = new DivFieldView($column); 17 | $html = $field->render(); 18 | 19 | $xml = simplexml_load_string($html); 20 | $this->assertEquals('v-field formkit-widget-textinput', (string)$xml->attributes()['class']); 21 | $div = $xml->div[0]; 22 | $this->assertEquals('label', (string)$div->attributes()['class']); 23 | $this->assertEquals('* Name', (string)$div->label); 24 | 25 | $label = $div->label; 26 | $this->assertEquals('formkit-widget formkit-label formkit-widget-label', (string)$label->attributes()['class']); 27 | 28 | $div = $xml->div[1]; 29 | $this->assertEquals('input', (string)$div->attributes()['class']); 30 | $input = $div->input->attributes(); 31 | $this->assertEquals('formkit-widget formkit-widget-text', $input->class); 32 | $this->assertEquals('name', $input->name); 33 | $this->assertEquals('text', $input->type); 34 | $this->assertEquals('John', $input->value); 35 | } 36 | } 37 | 38 | /* 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | */ 48 | 49 | -------------------------------------------------------------------------------- /src/GeneratedAction.php: -------------------------------------------------------------------------------- 1 | className = $className; 19 | $this->code = $code; 20 | $this->object = $object; 21 | } 22 | 23 | public function requireAt($path) 24 | { 25 | $this->writeTo($path); 26 | require $path; 27 | } 28 | 29 | public function writeTo($path) 30 | { 31 | if (false === file_put_contents($path, $this->code)) { 32 | throw new UnableToWriteCacheException("Can not write action class cache file: $cacheFile"); 33 | } 34 | } 35 | 36 | public function getRequiredPath() 37 | { 38 | return $this->requiredPath; 39 | } 40 | 41 | public function load() 42 | { 43 | $this->requiredPath = $tmpname = tempnam('/tmp', md5($this->className)); 44 | $this->requireAt($tmpname); 45 | return $tmpname; 46 | } 47 | 48 | public function getPsr4ClassPath($namespacePrefix) 49 | { 50 | $class = ltrim($this->className, '\\'); 51 | $class = str_replace($namespacePrefix, '', $class); 52 | return str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; 53 | } 54 | 55 | public function getPsrClassPath() 56 | { 57 | return str_replace('\\', DIRECTORY_SEPARATOR, ltrim($this->className, '\\')) . '.php'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Loggable.php: -------------------------------------------------------------------------------- 1 | 'File field %1 is required.', 7 | 'param.required' => 'Field %1 is required.', 8 | 'validation.error' => 'Please check your input.', 9 | 'csrf.token_expired' => 'CSRF token is expired, please refresh your page.', 10 | 'csrf.token_mismatch' => 'CSRF Token mismatched.', 11 | 'csrf.token_invalid' => 'CSRF token invalid.', 12 | 13 | // record action messages 14 | 'record_action.primary_key_is_required' => 'Updating record requires primary key value.', 15 | 'record_action.load_failed' => 'Can not load record.', 16 | 'record_action.record_not_found' => '%1 record not found.', 17 | 'record_action.validation_error' => '%1 validation failed.', 18 | 'record_action.successful_update' => '%1 record is updated successfully.', 19 | 'record_action.failed_update' => '%1 record update failed.', 20 | 21 | 'bulk_delete.successful_delete' => '%1 items were deleted successfully.', 22 | 23 | 'record_action.successful_create' => "%1 record is created successfully.", 24 | 'record_action.failed_create' => "%1 record create failed.", 25 | 26 | 'record_action.successful_delete' => "%1 record is deleted successfully.", 27 | 'record_action.failed_delete' => "%1 record delete failed.", 28 | ]; 29 | -------------------------------------------------------------------------------- /src/Messages/zh_TW.php: -------------------------------------------------------------------------------- 1 | '請上傳檔案欄位 %1', 7 | 'param.required' => '請輸入欄位 %1', 8 | 'validation.error' => '請檢查表單欄位是否正確。', 9 | 'csrf.token_expired' => '跨站請求認證碼逾期,請重新載入頁面', 10 | 'csrf.token_mismatch' => '跨站請求認證碼不符合,請重試', 11 | 'csrf.token_invalid' => '跨站請求認證碼不合法', 12 | 13 | 'record_action.primary_key_is_required' => '需要鍵值', 14 | 'record_action.load_failed' => '無法載入資料', 15 | 'record_action.record_not_found' => '%1 找不到資料', 16 | 'record_action.validation_error' => '%1 表單資料驗證失敗', 17 | 'record_action.successful_update' => '%1 成功更新', 18 | 'record_action.failed_update' => '%1 更新失敗.', 19 | 20 | /* bulk delete action */ 21 | 'bulk_delete.successful_delete' => '%1 個項目已成功刪除', 22 | 23 | 24 | // record action messages 25 | 'record_action.successful_create' => "%1 成功建立", 26 | 'record_action.failed_create' => "%1 無法建立", 27 | 28 | 'record_action.successful_delete' => "%1 成功刪除", 29 | 'record_action.failed_delete' => "%1 無法刪除", 30 | ]; 31 | -------------------------------------------------------------------------------- /src/MixinAction.php: -------------------------------------------------------------------------------- 1 | _action = $action; 16 | } 17 | 18 | public function preinit() 19 | { 20 | } 21 | 22 | public function postinit() 23 | { 24 | } 25 | 26 | public function beforeRun() 27 | { 28 | return true; 29 | } 30 | 31 | public function afterRun() 32 | { 33 | return true; 34 | } 35 | 36 | public function run() 37 | { 38 | return true; 39 | } 40 | 41 | public function schema() 42 | { 43 | /* 44 | $this->param('...'); 45 | */ 46 | } 47 | 48 | public function __get($k) 49 | { 50 | return $this->_action->$k; 51 | } 52 | 53 | public function __set($k, $v) 54 | { 55 | return $this->_action->$k = $v; 56 | } 57 | 58 | public function __call($m, $args) 59 | { 60 | if (method_exists($this->_action, $m)) { 61 | return call_user_func_array(array($this->_action,$m), $args); 62 | } else { 63 | throw new RuntimeException("Method $m is not defined in " . get_class($this)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Param/Image/CropAndScaleResize.php: -------------------------------------------------------------------------------- 1 | param = $param; 13 | } 14 | 15 | public function label() 16 | { 17 | return _('Crop Then Scale'); 18 | } 19 | 20 | public function resize($targetPath) 21 | { 22 | if (isset($this->param->size['height']) 23 | && isset($this->param->size['width'])) { 24 | $h = intval($this->param->size['height']); 25 | $w = intval($this->param->size['width']); 26 | $image = new ImageProcessor; 27 | $image->load($targetPath); 28 | 29 | $size = getimagesize($targetPath); 30 | if ($size[0] > $w || $size[1] > $h) { 31 | $image->cropOuterAndScale($w, $h); 32 | } 33 | $image->save($targetPath, null, $this->param->compression); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Param/Image/MaxHeightResize.php: -------------------------------------------------------------------------------- 1 | param = $param; 13 | } 14 | 15 | public function label() 16 | { 17 | return _('Fit To Height'); 18 | } 19 | 20 | public function resize($targetPath) 21 | { 22 | if ($this->param->resizeHeight) { 23 | $maxHeight = $this->param->resizeHeight; 24 | } elseif (isset($this->param->size['height'])) { 25 | $maxHeight = $this->param->size['height']; 26 | } 27 | 28 | 29 | if ($maxHeight) { 30 | $image = new ImageProcessor; 31 | $image->load($targetPath); 32 | 33 | // we should only resize image file only when size is changed. 34 | if ($image->getHeight() > $maxHeight) { 35 | $image->resizeToHeight($maxHeight); 36 | // (filename, image type, jpeg compression, permissions); 37 | $image->save($targetPath, null, $this->param->compression); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Param/Image/MaxWidthResize.php: -------------------------------------------------------------------------------- 1 | param = $param; 14 | } 15 | 16 | public function label() 17 | { 18 | return _('Fit To Width'); 19 | } 20 | 21 | public function resize($targetPath) 22 | { 23 | if ($this->param->resizeWidth) { 24 | $maxWidth = $this->param->resizeWidth; 25 | } elseif (isset($this->param->size['width'])) { 26 | $maxWidth = $this->param->size['width']; 27 | } 28 | 29 | 30 | if ($maxWidth) { 31 | $image = new ImageProcessor; 32 | $image->load($targetPath); 33 | 34 | // we should only resize image file only when size is changed. 35 | if ($image->getWidth() > $maxWidth) { 36 | $image->resizeToWidth($maxWidth); 37 | // (filename, image type, jpeg compression, permissions); 38 | $image->save($targetPath, null, $this->param->compression); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Param/Image/ScaleResize.php: -------------------------------------------------------------------------------- 1 | param = $param; 13 | } 14 | 15 | public function label() 16 | { 17 | return _('Scale'); 18 | } 19 | 20 | public function resize($targetPath) 21 | { 22 | if (isset($this->param->size['height']) 23 | && isset($this->param->size['width'])) { 24 | $h = $this->param->size['height']; 25 | $w = $this->param->size['width']; 26 | 27 | $image = new ImageProcessor; 28 | $image->load($targetPath); 29 | $image->resize($w, $h); 30 | 31 | // (filename, image type, jpeg compression, permissions); 32 | $image->save($targetPath, null, $this->param->compression); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Param/ImageParamTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($image->size(['width' => 100, 'height' => 200])); 13 | $this->assertNotNull($image->autoResize(false)); 14 | $this->assertNotNull($image->autoResize(true)); 15 | } 16 | 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/Param/ImageResizer.php: -------------------------------------------------------------------------------- 1 | param('image','Image') 21 | * ->validExtensions('jpg','png'); 22 | * } 23 | * 24 | */ 25 | 26 | class ImageResizer 27 | { 28 | public static $classes = array( 29 | 'max_width' => 'ActionKit\\Param\\Image\\MaxWidthResize', 30 | 'max_height' => 'ActionKit\\Param\\Image\\MaxHeightResize', 31 | 'scale' => 'ActionKit\\Param\\Image\\ScaleResize', 32 | 'crop_and_scale' => 'ActionKit\\Param\\Image\\CropAndScaleResize', 33 | ); 34 | 35 | public static function create($type, Param $param) 36 | { 37 | if (!isset(self::$classes[$type])) { 38 | throw new Exception("Image Resize Type '$type' is undefined."); 39 | } 40 | $c = self::$classes[$type]; 41 | return new $c($param); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Param/ParamTest.php: -------------------------------------------------------------------------------- 1 | required(); 14 | $this->assertNotNull($p->required); 15 | } 16 | 17 | public function testDefaultValueByScalar() 18 | { 19 | $p = new Param('name', new Action); 20 | $p->default('John'); 21 | $this->assertEquals('John', $p->getDefaultValue()); 22 | } 23 | 24 | public function testDefaultValueByClosure() 25 | { 26 | $p = new Param('created_at', new Action); 27 | $p->default(function() { 28 | return new DateTime; 29 | }); 30 | $this->assertInstanceOf(DateTime::class, $p->getDefaultValue()); 31 | } 32 | 33 | public function testValidValuesByArray() 34 | { 35 | $p = new Param('type', new Action); 36 | $p->validValues([ 37 | 'user', 38 | 'admin', 39 | 'guest', 40 | ]); 41 | $validValues = $p->getValidValues(); 42 | $this->assertEquals([ 43 | 'user', 44 | 'admin', 45 | 'guest', 46 | ], $validValues); 47 | } 48 | 49 | 50 | public function testValidValuesByClosure() 51 | { 52 | $p = new Param('type', new Action); 53 | $p->validValues(function() { 54 | return [ 'user', 'admin', 'guest' ]; 55 | }); 56 | $this->assertTrue($p->isValidValue('user')); 57 | $this->assertFalse($p->isValidValue('foo')); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/RecordAction/BulkDeleteRecordAction.php: -------------------------------------------------------------------------------- 1 | loadRecords(); 16 | foreach ($records as $record) { 17 | $delete = $record->asDeleteAction(); 18 | $delete->run(); 19 | } 20 | $msg = $this->messagePool->translate('bulk_delete.successful_delete', count($records)); 21 | return $this->success($msg); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/RecordAction/BulkRecordAction.php: -------------------------------------------------------------------------------- 1 | param('items'); 20 | } 21 | 22 | public function runValidate() 23 | { 24 | if (isset($this->args['items'])) { 25 | return true; // no error 26 | } 27 | return false; 28 | } 29 | 30 | // TODO: we should use 31 | // collection and where id in (1,2,3) to improve performance. 32 | public function loadRecords() 33 | { 34 | $itemIds = $this->arg('items'); 35 | $records = array(); 36 | foreach ($itemIds as $id) { 37 | $record = new $this->recordClass; 38 | $record->load((int) $id); 39 | if ($record->id) { 40 | $records[] = $record; 41 | } 42 | } 43 | return $records; 44 | } 45 | 46 | public function run() 47 | { 48 | $records = $this->loadRecords(); 49 | foreach ($records as $record) { 50 | $ret = $record->delete(); 51 | } 52 | 53 | return $this->deleteSuccess($ret); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/RecordAction/BulkZhConvertRecordAction.php: -------------------------------------------------------------------------------- 1 | getData(); 19 | $newArgs = array(); 20 | foreach ($this->convertionKeys as $key) { 21 | if (! isset($args[$key])) { 22 | continue; 23 | } 24 | $newArgs[ $key ] = call_user_func($convertion, $args[ $key ]); 25 | } 26 | $record->update($newArgs); 27 | } 28 | } 29 | 30 | public function run() 31 | { 32 | kernel()->library->load('han-convert'); 33 | $convertion = $this->arg('convertion'); 34 | if (! in_array($convertion, $this->convertionFunctions)) { 35 | return $this->error('Invalid convertion method.'); 36 | } 37 | 38 | $records = $this->loadRecords(); 39 | $this->convertRecords($convertion, $records); 40 | 41 | return $this->success(count($records) . '個項目轉換成功'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/RecordAction/CreateRecordAction.php: -------------------------------------------------------------------------------- 1 | recordResult = $ret = $this->recordClass::create($args); 11 | if ($ret->error) { 12 | $this->convertRecordValidation($ret); 13 | return $this->createError($ret); 14 | } 15 | $this->record = $this->recordClass::findByPrimaryKey($ret->key); 16 | $this->result->data($this->record->getData()); 17 | return $this->createSuccess($ret); 18 | } 19 | 20 | protected function filterArguments(array $args) 21 | { 22 | if ($this->takeFields) { 23 | // take these fields only 24 | return array_intersect_key($args, array_fill_keys($this->takeFields, 1)); 25 | } elseif ($this->filterOutFields) { 26 | return array_diff_key($args, array_fill_keys($this->filterOutFields, 1)); 27 | } 28 | return $args; 29 | } 30 | 31 | /** 32 | * runValidate inherited from parent class. 33 | * */ 34 | public function run() 35 | { 36 | $ret = $this->create($this->args); 37 | if ($ret === false) { 38 | return $ret; 39 | } 40 | if ($this->nested && ! empty($this->relationships)) { 41 | return $this->processSubActions(); 42 | } 43 | return $ret; 44 | } 45 | 46 | public function successMessage($ret) 47 | { 48 | return $this->messagePool->translate('record_action.successful_create', $this->record->getLabel()); 49 | } 50 | 51 | public function errorMessage($ret) 52 | { 53 | // XXX: should show exception message when error is found. 54 | if ($ret->exception) { 55 | return __('Can not create %1 record: %2', $this->record->getLabel(), $ret->exception->getMessage()); 56 | } 57 | return $this->messagePool->translate('record_action.failed_create', $this->record->getLabel()); 58 | } 59 | 60 | public function createSuccess($ret) 61 | { 62 | return $this->success($this->successMessage($ret), array( 63 | 'id' => $this->record->id 64 | )); 65 | } 66 | 67 | public function createError($ret) 68 | { 69 | return $this->error($this->errorMessage($ret)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/RecordAction/DeleteRecordAction.php: -------------------------------------------------------------------------------- 1 | record; 13 | $schema = $record->getSchema(); 14 | $data = $record->getData(); 15 | foreach ($data as $name => $val) { 16 | if ($val == null) { 17 | continue; 18 | } 19 | $column = $schema->getColumn($name); 20 | switch ($column->contentType) { 21 | case "ImageFile": 22 | case "File": 23 | if ($this->unlink && file_exists($val)) { 24 | unlink($val); 25 | } 26 | break; 27 | } 28 | } 29 | 30 | return $this->doDelete($this->args); 31 | } 32 | 33 | public function doDelete($args) 34 | { 35 | $backup = clone $this->record; 36 | $ret = $this->record->delete(); 37 | if ($ret->success) { 38 | $this->record = $backup; 39 | return $this->deleteSuccess($ret); 40 | } else { 41 | return $this->deleteError($ret); 42 | } 43 | } 44 | 45 | /** 46 | * @inherit 47 | */ 48 | public function runValidate() 49 | { 50 | if (isset($this->args['id'])) { 51 | return true; 52 | } 53 | return false; 54 | } 55 | 56 | public function successMessage($ret) 57 | { 58 | return $this->messagePool->translate('record_action.successful_delete', $this->record->getLabel()); 59 | } 60 | 61 | public function errorMessage($ret) 62 | { 63 | return $this->messagePool->translate('record_action.failed_delete', $this->record->getLabel()); 64 | } 65 | 66 | public function deleteSuccess($ret) 67 | { 68 | return $this->success($this->successMessage($ret), array( 'id' => $this->record->id)); 69 | } 70 | 71 | public function deleteError($ret) 72 | { 73 | return $this->error($this->errorMessage($ret)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/RecordAction/UpdateOrderingRecordAction.php: -------------------------------------------------------------------------------- 1 | param('list')->isa('str'); 30 | } 31 | 32 | 33 | public function loadRecord($key) 34 | { 35 | return $this->recordClass::findByPrimaryKey($key); 36 | } 37 | 38 | public function runUpdateList() 39 | { 40 | if ($this->mode !== self::MODE_INCREMENTALLY) { 41 | throw new Exception("Unsupported sort mode"); 42 | } 43 | if ($orderingList = json_decode($this->arg('list'))) { 44 | foreach ($orderingList as $ordering) { 45 | $record = $this->loadRecord($ordering->record); 46 | $ret = $record->update(array( $this->targetColumn => $ordering->ordering )); 47 | if ($ret->error) { 48 | throw new Exception("Record update failed: {$ret->message}"); 49 | } 50 | } 51 | } 52 | } 53 | 54 | public function run() 55 | { 56 | try { 57 | $this->runUpdateList(); 58 | } catch (Exception $e) { 59 | return $this->error("Ordering Update Failed: {$e->getMessage()}"); 60 | } 61 | return $this->success('排列順序已更新'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ResultTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($result); 11 | 12 | $result->success('Success Tset'); 13 | $this->assertEquals('success', $result->type); 14 | $this->assertEquals(true, $result->isSuccess()); 15 | $this->assertNotNull($result->message); 16 | 17 | $this->assertNotNull($result->error('Error Tset')); 18 | $this->assertEquals('error', $result->type); 19 | $this->assertEquals(true, $result->isError()); 20 | $this->assertNotNull($result->getMessage()); 21 | 22 | $this->assertNotNull($result->valid('Valid Tset')); 23 | $this->assertEquals('valid', $result->type); 24 | $this->assertEquals(true, $result->isValidation()); 25 | 26 | $this->assertNotNull($result->invalid('Valid Tset')); 27 | $this->assertEquals('invalid', $result->type); 28 | $this->assertEquals(true, $result->isValidation()); 29 | 30 | $this->assertNotNull($result->completion('country', 'list', ['tw', 'jp', 'us'])); 31 | $this->assertEquals('completion', $result->type); 32 | $this->assertEquals(true, $result->isCompletion()); 33 | 34 | $this->assertNotNull($result->desc('description')); 35 | $this->assertNotNull($result->debug('debug')); 36 | 37 | $this->assertNotNull($result->toArray()); 38 | $this->assertNotNull($result->__toString()); 39 | } 40 | 41 | public function testResultData() 42 | { 43 | $result = new Result; 44 | $this->assertNotNull($result->data(['data1' => 'value1', 'data2' => 'value2'])); 45 | $this->assertNotNull($result->data('data1', 'value1')); 46 | $this->assertNotNull($result->addData('data2', 'value2')); 47 | $this->assertNotNull($result->mergeData(['data3' => 'value3', 'data4' => 'value4'])); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ServiceContainer.php: -------------------------------------------------------------------------------- 1 | preset(); 33 | } 34 | 35 | protected function preset() 36 | { 37 | $self = $this; 38 | 39 | // the default parameter 40 | $this['locale'] = 'en'; 41 | 42 | // the default cache dir 43 | $this['cache_dir'] = __DIR__ . DIRECTORY_SEPARATOR . 'Cache'; 44 | 45 | $this['message_directory'] = __DIR__ . DIRECTORY_SEPARATOR . 'Messages'; 46 | 47 | $this['message_pool'] = function ($c) { 48 | return new MessagePool($c['locale'], $c['message_directory']); 49 | }; 50 | 51 | $this['csrf'] = function ($c) { 52 | return new CsrfTokenProvider(new CsrfSessionStorage('__csrf_token')); 53 | }; 54 | 55 | // This factory will always generate new csrf token 56 | $this['csrf_token_new'] = $this->factory(function ($c) { 57 | return $c['csrf']->loadCurrentToken($refresh = true); 58 | }); 59 | 60 | // Create csrf token on demain 61 | $this['csrf_token'] = $this->factory(function ($c) { 62 | $provider = $c['csrf']; 63 | // try to load csrf token in the current session 64 | $token = $provider->loadCurrentToken(); 65 | if ($token == null || $token->isExpired($_SERVER['REQUEST_TIME'])) { 66 | // generate a new token 67 | return $provider->loadCurrentToken(true); 68 | } 69 | return $token; 70 | }); 71 | 72 | // The default twig loader 73 | $this['twig_loader'] = function ($c) { 74 | $refClass = new ReflectionClass('ActionKit\\ActionGenerator'); 75 | $templateDirectory = dirname($refClass->getFilename()) . DIRECTORY_SEPARATOR . 'Templates'; 76 | 77 | // add ActionKit built-in template path 78 | $loader = new Twig_Loader_Filesystem([]); 79 | $loader->addPath($templateDirectory, 'ActionKit'); 80 | return $loader; 81 | }; 82 | 83 | $this['generator'] = function ($c) { 84 | return new ActionGenerator; 85 | }; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Storage/FilePath.php: -------------------------------------------------------------------------------- 1 | dirname = $info['dirname']; 23 | $this->extension = $info['extension']; 24 | $this->filename = $info['filename']; 25 | $this->basename = $info['basename']; 26 | } 27 | 28 | /** 29 | * Append a suffix to the current filename. 30 | */ 31 | public function appendFilenameSuffix($suffix) 32 | { 33 | $this->filename = "{$this->filename}{$suffix}"; 34 | } 35 | 36 | public function exists() 37 | { 38 | $p = $this->__toString(); 39 | return file_exists($p); 40 | } 41 | 42 | public function appendFilenameTimestamp() 43 | { 44 | $timestamp = time(); 45 | $this->filename = "{$this->filename}_{$timestamp}"; 46 | } 47 | 48 | public function appendFilenameUniqid($prefix = null) 49 | { 50 | $uniqid = uniqid($prefix); 51 | $this->filename = "{$this->filename}_{$uniqid}"; 52 | } 53 | 54 | 55 | /** 56 | * strip special charactor 57 | */ 58 | public function strip() 59 | { 60 | $this->filename = preg_replace('/\W+/', '_', $this->filename); 61 | $this->filename = preg_replace('/_{2,}/', '_', $this->filename); 62 | $this->filename = preg_replace('/_+$/', '', $this->filename); 63 | } 64 | 65 | 66 | /** 67 | * The rename method returns a new FilePath to copy the instance. 68 | * 69 | * @return FilePath 70 | */ 71 | public function renameAs($newfilename) 72 | { 73 | $newp = clone $this; 74 | $newp->filename = $newfilename; 75 | return $newp; 76 | } 77 | 78 | public function __toString() 79 | { 80 | return "{$this->dirname}/{$this->filename}.{$this->extension}"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Storage/FilePathTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($p, $path->__toString()); 27 | } 28 | 29 | public function testFilenameRename() 30 | { 31 | $p = new FilePath('upload/test_中文.jpg'); 32 | $p2 = $p->renameAs('foo'); 33 | $this->assertEquals('upload/foo.jpg', $p2->__toString()); 34 | } 35 | 36 | public function testUniqid() 37 | { 38 | $p = new FilePath('upload/foo.jpg'); 39 | $p->appendFilenameUniqid(); 40 | $this->assertRegExp('!upload/foo_\w+.jpg!', $p->__toString()); 41 | } 42 | 43 | public function testStrip() 44 | { 45 | $p = new FilePath('upload/test_(1200x300)_中文.jpg'); 46 | $p->strip(); 47 | $this->assertEquals('upload/test_1200x300.jpg', $p->__toString()); 48 | } 49 | 50 | 51 | public function testSuffix() 52 | { 53 | $p = new FilePath('upload/test.jpg'); 54 | $p->appendFilenameSuffix('_foo'); 55 | $this->assertEquals('upload/test_foo.jpg', $p->__toString()); 56 | } 57 | 58 | public function testExists() 59 | { 60 | $p = new FilePath('src/Action.php'); 61 | $this->assertTrue($p->exists()); 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/Storage/FileRename/Md5Rename.php: -------------------------------------------------------------------------------- 1 | renameAs($md5); 19 | return $p2->__toString(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Storage/FileRenameMethodsTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('/tmp/upload/8b1a9953c4611296a827abf8c47804d7.jpg', $newp); 16 | } 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Template.php: -------------------------------------------------------------------------------- 1 | config = $config; 23 | } 24 | 25 | public function init() 26 | { 27 | $dir = $this->getTemplateDir(); 28 | if (! file_exists($dir)) { 29 | throw RuntimeException("Directory $dir for TemplateView does not exist."); 30 | } 31 | $this->loader = new Twig_Loader_Filesystem($dir); 32 | $this->environment = new Twig_Environment($this->loader, $this->config); 33 | } 34 | 35 | public function setClassDirFrom($object) 36 | { 37 | $ref = new ReflectionObject($object); 38 | return $this->_classDir = dirname($ref->getFilename()); 39 | } 40 | 41 | public function getClassDir() 42 | { 43 | if ($this->_classDir) { 44 | return $this->_classDir; 45 | } 46 | return $this->setClassDirFrom($this); 47 | } 48 | 49 | public function getTemplateDir() 50 | { 51 | return $this->getClassDir() . DIRECTORY_SEPARATOR . 'Templates'; 52 | } 53 | 54 | 55 | /** 56 | * $template->render('@ActionKit/index.html', array('the' => 'variables', 'go' => 'here')); 57 | */ 58 | public function render($templateFile, $arguments = array()) 59 | { 60 | $template = $this->environment->loadTemplate($templateFile); 61 | return $template->render($arguments); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Templates/RecordAction.html.twig: -------------------------------------------------------------------------------- 1 | assertNotNull($generatedAction); 16 | $generatedAction->load(); 17 | $this->assertTrue(class_exists($className), "$className exists"); 18 | } 19 | 20 | public function assertActionInvokeSuccess(Action $action) 21 | { 22 | $ret = $action->invoke(); 23 | $result = $action->getResult(); 24 | $this->assertTrue($ret, $result->message); 25 | $this->assertEquals('success', $result->type, $result->message); 26 | return $result; 27 | } 28 | 29 | public function assertActionInvokeFail(Action $action) 30 | { 31 | $ret = $action->invoke(); 32 | $result = $action->getResult(); 33 | $this->assertFalse($ret, $result->message); 34 | $this->assertEquals('error', $result->type, $result->message); 35 | return $result; 36 | } 37 | 38 | public static function assertStringEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false) 39 | { 40 | if (!file_exists($expectedFile)) { 41 | file_put_contents($expectedFile, $actualString); 42 | echo PHP_EOL, "Added expected file: ", $expectedFile, PHP_EOL; 43 | echo "=========================================", PHP_EOL; 44 | echo $actualString, PHP_EOL; 45 | echo "=========================================", PHP_EOL; 46 | } 47 | return parent::assertStringEqualsFile($expectedFile, $actualString, $message, $canonicalize, $ignoreCase); 48 | } 49 | 50 | public static function assertFileEquals($expectedFile, $actualFile, $message = '', $canonicalize = false, $ignoreCase = false) 51 | { 52 | if (!file_exists($expectedFile)) { 53 | copy($actualFile, $expectedFile); 54 | echo PHP_EOL, "Added expected file: ", $expectedFile, PHP_EOL; 55 | echo "=========================================", PHP_EOL; 56 | echo file_get_contents($expectedFile), PHP_EOL; 57 | echo "=========================================", PHP_EOL; 58 | } 59 | return parent::assertFileEquals($expectedFile, $actualFile, $message, $canonicalize, $ignoreCase); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Testing/ActionTestCase.php: -------------------------------------------------------------------------------- 1 | getType() : null; 57 | if (!$filetype) { 58 | $mt = new MimeTypes; 59 | $filetype = $mt->resolveMimeType($path); 60 | } 61 | $pathinfo = pathinfo($path); 62 | $file = array( 63 | 'name' => $pathinfo['basename'], 64 | 'tmp_name' => $path, 65 | 'type' => $filetype, 66 | 'saved_path' => $path, 67 | 'size' => filesize($path) 68 | ); 69 | return $file; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ValueType/BaseType.php: -------------------------------------------------------------------------------- 1 | assertSame($success, $bool->test($input)); 34 | $this->assertSame($expected, $bool->parse($input)); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/ValueType/DateTimeType.php: -------------------------------------------------------------------------------- 1 | format(DateTime::ATOM); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ValueType/DateTimeTypeTest.php: -------------------------------------------------------------------------------- 1 | assertSame($expected, $type->test($input)); 26 | } 27 | 28 | public function testDateTimeTypeParse() 29 | { 30 | $bool = new DateTimeType; 31 | $this->assertNotNull($bool->parse('2015-01-01')); 32 | $this->assertNotNull($bool->parse(date('c'))); 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/ValueType/DirType.php: -------------------------------------------------------------------------------- 1 | getPathname(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ValueType/EmailType.php: -------------------------------------------------------------------------------- 1 | getPathname(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ValueType/IntType.php: -------------------------------------------------------------------------------- 1 | assertSame($success, $bool->test($input)); 32 | $this->assertSame($expect, $bool->parse($input)); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/ValueType/IpType.php: -------------------------------------------------------------------------------- 1 | assertNotNull(new IpType ); 10 | $this->assertNotNull(new Ipv4Type ); 11 | $this->assertNotNull(new Ipv6Type ); 12 | } 13 | 14 | public function testIpType() 15 | { 16 | $ip = new IpType; 17 | $this->assertTrue( $ip->test('192.168.25.58') ); 18 | $this->assertTrue( $ip->test('2607:f0d0:1002:51::4') ); 19 | $this->assertTrue( $ip->test('::1') ); 20 | $this->assertFalse($ip->test('10.10.15.10/16')); 21 | } 22 | 23 | public function testIpv4Type() 24 | { 25 | $ipv4 = new Ipv4Type; 26 | $this->assertTrue( $ipv4->test('192.168.25.58') ); 27 | $this->assertTrue( $ipv4->test('8.8.8.8') ); 28 | $this->assertFalse($ipv4->test('2607:f0d0:1002:51::4')); 29 | } 30 | 31 | public function testIpv6Type() 32 | { 33 | $ipv6 = new Ipv6Type; 34 | $this->assertTrue($ipv6->test('2607:f0d0:1002:51::4') ); 35 | $this->assertTrue($ipv6->test('2607:f0d0:1002:0051:0000:0000:0000:0004') ); 36 | $this->assertFalse($ipv6->test('192.168.25.58')); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/ValueType/Ipv4Type.php: -------------------------------------------------------------------------------- 1 | "bar" ], true], 14 | ["", null, true], 15 | ]; 16 | } 17 | 18 | /** 19 | * @dataProvider jsonDataProvider 20 | */ 21 | public function testJsonType($input, $expected, $success) 22 | { 23 | $type = new JsonType; 24 | $this->assertSame($success, $type->test($input)); 25 | 26 | if ($expected instanceof \Object) { 27 | $this->assertSame($expected, $type->parse($input)); 28 | } else { 29 | $this->assertEquals($expected, $type->parse($input)); 30 | } 31 | } 32 | } 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/ValueType/NumType.php: -------------------------------------------------------------------------------- 1 | getPathname(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ValueType/RegexType.php: -------------------------------------------------------------------------------- 1 | option)) { 12 | return false; 13 | } 14 | $pm = preg_match($this->option, $value); 15 | if ($pm == 0) { 16 | $pm = false; 17 | } 18 | 19 | return $pm; 20 | } 21 | 22 | public function parse($value) 23 | { 24 | return strval($value); 25 | } 26 | 27 | public function deflate($value) 28 | { 29 | return $value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ValueType/StrType.php: -------------------------------------------------------------------------------- 1 | assertNotNull(new BoolType); 10 | $this->assertNotNull(new StrType); 11 | $this->assertNotNull(new FileType ); 12 | $this->assertNotNull(new NumType ); 13 | $this->assertNotNull(new UrlType ); 14 | $this->assertNotNull(new IpType ); 15 | $this->assertNotNull(new Ipv4Type); 16 | $this->assertNotNull(new Ipv6Type); 17 | $this->assertNotNull(new EmailType); 18 | $this->assertNotNull(new PathType); 19 | } 20 | 21 | public function testBoolType() 22 | { 23 | $bool = new BoolType; 24 | $this->assertTrue( $bool->test('true') ); 25 | $this->assertTrue( $bool->test('false') ); 26 | $this->assertTrue( $bool->test('0') ); 27 | $this->assertTrue( $bool->test('1') ); 28 | $this->assertFalse( $bool->test('foo') ); 29 | $this->assertFalse( $bool->test('123') ); 30 | } 31 | 32 | public function testPathType() 33 | { 34 | $url = new PathType; 35 | $this->assertTrue( $url->test('tests') ); 36 | $this->assertTrue($url->test('composer.json') ); 37 | $this->assertFalse($url->test('foo/bar')); 38 | } 39 | 40 | public function testUrlType() 41 | { 42 | $url = new UrlType; 43 | $this->assertTrue($url->test('http://t')); 44 | $this->assertTrue($url->test('http://t.c')); 45 | $this->assertFalse($url->test('t.c')); 46 | } 47 | 48 | public function testIpType() 49 | { 50 | $ip = new IpType; 51 | $this->assertTrue( $ip->test('192.168.25.58') ); 52 | $this->assertTrue( $ip->test('2607:f0d0:1002:51::4') ); 53 | $this->assertTrue( $ip->test('::1') ); 54 | $this->assertFalse($ip->test('10.10.15.10/16')); 55 | } 56 | 57 | public function testIpv4Type() 58 | { 59 | $ipv4 = new Ipv4Type; 60 | $this->assertTrue( $ipv4->test('192.168.25.58') ); 61 | $this->assertTrue( $ipv4->test('8.8.8.8') ); 62 | $this->assertFalse($ipv4->test('2607:f0d0:1002:51::4')); 63 | } 64 | 65 | public function testIpv6Type() 66 | { 67 | $ipv6 = new Ipv6Type; 68 | $this->assertTrue($ipv6->test('2607:f0d0:1002:51::4') ); 69 | $this->assertTrue($ipv6->test('2607:f0d0:1002:0051:0000:0000:0000:0004') ); 70 | $this->assertFalse($ipv6->test('192.168.25.58')); 71 | } 72 | 73 | public function testEmailType() 74 | { 75 | $email = new EmailType; 76 | $this->assertTrue($email->test('test@gmail.com')); 77 | $this->assertFalse($email->test('test@test')); 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/View/StackViewTest.php: -------------------------------------------------------------------------------- 1 | param('first_name') 14 | ->label('First name') 15 | ->renderAs('TextInput'); 16 | 17 | $this->param('last_name') 18 | ->label('Last name') 19 | ->renderAs('TextInput'); 20 | 21 | $this->param('role') 22 | ->label('Role') 23 | ->validValues(array( 'Admin', 'User' )) 24 | ->renderAs('SelectInput'); 25 | } 26 | 27 | public function run() 28 | { 29 | return $this->success('Created!'); 30 | } 31 | } 32 | 33 | /** 34 | * @group maghead 35 | */ 36 | use Maghead\Testing\ModelTestCase; 37 | use ProductBundle\Model\ProductSchema; 38 | use ProductBundle\Model\Category; 39 | use ProductBundle\Model\CategorySchema; 40 | use ProductBundle\Action\CreateProduct; 41 | 42 | class StackViewTest extends ModelTestCase 43 | { 44 | public function models() 45 | { 46 | return [new ProductSchema, new CategorySchema]; 47 | } 48 | 49 | public function testNestedView() 50 | { 51 | $c = new Category; 52 | $c->create(array( 'name' => 'Foo' )); 53 | 54 | $action = new CreateProduct; 55 | $view = $action->asView(StackView::class, array( 56 | 'no_form' => true, 57 | 'no_layout' => true, 58 | )); 59 | $this->assertNotNull($view); 60 | 61 | $view->buildRelationalActionViewForExistingRecords('categories'); 62 | $html = $view->getContainer()->render(); 63 | $this->assertNotNull($html); 64 | 65 | # $dom = new DOMDocument; 66 | # $dom->load($html); 67 | 68 | $c->delete(); 69 | } 70 | 71 | public function testBasicView() 72 | { 73 | $action = new CreateUserAction; 74 | $this->assertNotNull($action); 75 | 76 | $view = new StackView($action); 77 | $this->assertNotNull($view); 78 | 79 | $html = $view->render(); 80 | $this->assertNotNull($html); 81 | 82 | $resultDom = new DOMDocument; 83 | $resultDom->loadXML($html); 84 | 85 | $finder = new DomXPath($resultDom); 86 | 87 | $nodes = $finder->query("//form"); 88 | $this->assertEquals(1, $nodes->length); 89 | 90 | $nodes = $finder->query("//input"); 91 | $this->assertEquals(4, $nodes->length); 92 | 93 | $nodes = $finder->query("//*[contains(@class, 'formkit-widget')]"); 94 | $this->assertEquals(8, $nodes->length); 95 | 96 | $nodes = $finder->query("//*[contains(@class, 'formkit-widget-text')]"); 97 | $this->assertEquals(2, $nodes->length); 98 | 99 | $nodes = $finder->query("//*[contains(@class, 'formkit-label')]"); 100 | $this->assertEquals(3, $nodes->length); 101 | 102 | $nodes = $finder->query("//input[@name='last_name']"); 103 | $this->assertEquals(1, $nodes->length); 104 | 105 | $nodes = $finder->query("//input[@name='first_name']"); 106 | $this->assertEquals(1, $nodes->length); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/View/TemplateView.php: -------------------------------------------------------------------------------- 1 | action = $action; 20 | $this->template = new Template; 21 | $this->template->setClassDirFrom($this); 22 | $this->template->init(); 23 | } 24 | 25 | /** 26 | * $twig->render('index.html', array('the' => 'variables', 'go' => 'here')); 27 | * */ 28 | public function renderTemplateFile($templateFile, $arguments = array()) 29 | { 30 | $arguments = array_merge(array( 31 | // the view object. 32 | 'View' => $this, 33 | 34 | // the action object. 35 | 'Action' => $this->action 36 | ), $arguments); 37 | return $this->template->render($templateFile, $arguments); 38 | } 39 | 40 | public function __toString() 41 | { 42 | return $this->render(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/View/TemplateViewTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($action); 13 | 14 | $view = new \FooTemplateView($action); 15 | $this->assertNotNull($view); 16 | $this->assertNotNull($view->render()); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /tests/ActionTrait/RoleCheckerTest.php: -------------------------------------------------------------------------------- 1 | roles = $roles; 12 | } 13 | 14 | public function getRoles() 15 | { 16 | return $this->roles; 17 | } 18 | } 19 | 20 | class MyRoleChecker 21 | { 22 | 23 | use RoleChecker; 24 | 25 | public $allowedRoles; 26 | 27 | public function __construct(array $allowedRoles) 28 | { 29 | $this->allowedRoles = $allowedRoles; 30 | } 31 | 32 | } 33 | 34 | class RoleCheckerTest extends \PHPUnit\Framework\TestCase 35 | { 36 | public function testRoleAllowedRoleByString() 37 | { 38 | $checker = new MyRoleChecker(['admin']); 39 | $ret = $checker->currentUserCan('admin', 'run', []); 40 | $this->assertTrue($ret[0]); 41 | } 42 | 43 | public function testRoleDisallowedByString() 44 | { 45 | $checker = new MyRoleChecker(['admin']); 46 | $ret = $checker->currentUserCan('foo', 'run', []); 47 | $this->assertFalse($ret[0]); 48 | } 49 | 50 | public function testRoleAnonymousUserWithNull() 51 | { 52 | $checker = new MyRoleChecker(['admin']); 53 | $ret = $checker->currentUserCan(null, 'run', []); 54 | $this->assertFalse($ret[0]); 55 | } 56 | 57 | public function testRoleAllowedByUserObject() 58 | { 59 | $user = new MyUser(['admin']); 60 | $checker = new MyRoleChecker(['admin']); 61 | $ret = $checker->currentUserCan($user, 'run', []); 62 | $this->assertTrue($ret[0]); 63 | } 64 | 65 | public function testRoleDisallowedByUserObject() 66 | { 67 | $user = new MyUser(['user']); 68 | $checker = new MyRoleChecker(['admin']); 69 | $ret = $checker->currentUserCan($user, 'run', []); 70 | $this->assertFalse($ret[0]); 71 | } 72 | 73 | 74 | public function testGetAllowedRoles() 75 | { 76 | $checker = new MyRoleChecker(['admin']); 77 | $this->assertSame(['admin'],$checker->getAllowedRoles()); 78 | } 79 | 80 | 81 | /** 82 | * @expectedException Exception 83 | */ 84 | public function testUnsupportedCurrentUser() 85 | { 86 | $checker = new MyRoleChecker(['admin']); 87 | $checker->currentUserCan(false, 'run', []); 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /tests/ActionWithUserTest.php: -------------------------------------------------------------------------------- 1 | roles; 15 | } 16 | } 17 | 18 | /** 19 | * @group maghead 20 | */ 21 | class ActionWithUserTest extends \Maghead\Testing\ModelTestCase 22 | { 23 | public function models() 24 | { 25 | return [ new OrderSchema ]; 26 | } 27 | 28 | public function userProvider() 29 | { 30 | return array( 31 | array('memeber', 'error', true), 32 | array('admin', 'success', true), 33 | array('admin', 'error', false), 34 | ); 35 | } 36 | 37 | /** 38 | * @dataProvider userProvider 39 | */ 40 | public function testRunnerWithSimpleUser($roles, $resultType, $setUser) 41 | { 42 | $container = new ServiceContainer; 43 | $generator = $container['generator']; 44 | $generator->registerTemplate('RecordActionTemplate', new RecordActionTemplate); 45 | $runner = new ActionRunner($container); 46 | $runner->registerAutoloader(); 47 | $runner->registerAction('RecordActionTemplate', array( 48 | 'namespace' => 'OrderBundle', 49 | 'model' => 'Order', 50 | 'types' => array( 51 | ['prefix' => 'Create', 'allowed_roles' => ['user', 'admin'] ], 52 | ['prefix' => 'Update'], 53 | ['prefix' => 'Delete'] 54 | ) 55 | )); 56 | 57 | if($setUser) { 58 | $runner->setCurrentUser($roles); 59 | } 60 | $result = $runner->run('OrderBundle::Action::CreateOrder',[ 61 | 'quantity' => '1', 62 | 'amount' => 100, 63 | ]); 64 | $this->assertNotNull($result); 65 | $this->assertEquals($resultType, $result->type); 66 | } 67 | 68 | 69 | public function roleProvider() 70 | { 71 | return array( 72 | array(['member', 'manager'], 'error'), 73 | array(['member', 'user'], 'success'), 74 | ); 75 | } 76 | /** 77 | * @dataProvider roleProvider 78 | */ 79 | public function testRunnerWithMultiRoleInterface($roles, $resultType) 80 | { 81 | $container = new ServiceContainer; 82 | $generator = $container['generator']; 83 | $generator->registerTemplate('RecordActionTemplate', new RecordActionTemplate); 84 | $runner = new ActionRunner($container); 85 | $runner->registerAutoloader(); 86 | $runner->registerAction('RecordActionTemplate', array( 87 | 'namespace' => 'OrderBundle', 88 | 'model' => 'Order', 89 | 'types' => array( 90 | ['prefix' => 'Create', 'allowed_roles' => ['user', 'admin'] ], 91 | ['prefix' => 'Update'], 92 | ['prefix' => 'Delete'] 93 | ) 94 | )); 95 | 96 | $user = new TestUser; 97 | $user->roles = $roles; 98 | $runner->setCurrentUser($user); 99 | $result = $runner->run('OrderBundle::Action::CreateOrder',[ 100 | 'quantity' => '1', 101 | 'amount' => 100, 102 | ]); 103 | $this->assertNotNull($result); 104 | $this->assertEquals($resultType, $result->type); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/EmailFieldActionTest.php: -------------------------------------------------------------------------------- 1 | param('email') 10 | ->isa('Email'); 11 | } 12 | } 13 | 14 | 15 | 16 | class EmailFieldActionTest extends \PHPUnit\Framework\TestCase 17 | { 18 | 19 | 20 | public function testInvalidEmailFieldAction() 21 | { 22 | $action = new EmailFieldTestAction([ 'email' => 'yoanlin93' ]); 23 | $ret = $action->invoke(); 24 | $this->assertFalse($ret); 25 | } 26 | 27 | public function testEmailFieldAction() 28 | { 29 | $action = new EmailFieldTestAction([ 'email' => 'yoanlin93@gmail.com' ]); 30 | $ret = $action->invoke(); 31 | $this->assertTrue($ret); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /tests/FooTemplateView.php: -------------------------------------------------------------------------------- 1 | renderTemplateFile('foo.html',array( )); 8 | } 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/IntFieldActionTest.php: -------------------------------------------------------------------------------- 1 | param('cnt') 11 | ->isa('Int'); 12 | } 13 | 14 | } 15 | 16 | 17 | class IntFieldActionTest extends \PHPUnit\Framework\TestCase 18 | { 19 | 20 | public function testIntFieldAction() 21 | { 22 | $action = new IntFieldTestAction([ 'cnt' => 10 ]); 23 | $ret = $action->invoke(); 24 | $this->assertTrue($ret); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tests/Model/CRUDTest/FooUser.php: -------------------------------------------------------------------------------- 1 | column('username')->varchar(12); 8 | $schema->column('password')->varchar(12); 9 | } 10 | #boundary start 2d278467a6071e8ac2130d201b3510e1 11 | const schema_proxy_class = 'ActionKit\\Model\\CRUDTest\\FooUserSchemaProxy'; 12 | const collection_class = 'ActionKit\\Model\\CRUDTest\\FooUserCollection'; 13 | const model_class = 'ActionKit\\Model\\CRUDTest\\FooUser'; 14 | const table = 'foo_users'; 15 | #boundary end 2d278467a6071e8ac2130d201b3510e1 16 | } 17 | 18 | -------------------------------------------------------------------------------- /tests/Model/CRUDTest/FooUserCollection.php: -------------------------------------------------------------------------------- 1 | column('quantity') 9 | ->integer() 10 | ->required() 11 | ; 12 | 13 | $this->column('order_id') 14 | ->integer() 15 | ->required() 16 | ->unsigned() 17 | ->refer(OrderSchema::class); 18 | 19 | $this->column('subtotal')->integer(); 20 | 21 | $this->belongsTo('order', OrderSchema::class, 'id', 'order_id'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/OrderBundle/Model/OrderRepo.php: -------------------------------------------------------------------------------- 1 | column('sum') 12 | ->integer(); 13 | 14 | $this->column('quantity') 15 | ->integer(); 16 | 17 | $this->column('amount') 18 | ->notNull() 19 | ->integer(); 20 | 21 | $this->column('updated_at') 22 | ->timestamp() 23 | ->notNull() 24 | ->isa('DateTime') 25 | ->renderAs('DateTimeInput') 26 | ->default(new Raw('CURRENT_TIMESTAMP')) 27 | ->onUpdate(new Raw('CURRENT_TIMESTAMP')) 28 | ->label(_('更新時間')) 29 | ; 30 | 31 | $this->column('created_at') 32 | ->timestamp() 33 | ->isa('DateTime') 34 | ->null() 35 | ->renderAs('DateTimeInput') 36 | ->label( _('建立時間') ) 37 | ->default(function() { 38 | return new \DateTime; 39 | }) 40 | ; 41 | 42 | $this->hasMany('items', OrderItemSchema::class, 'order_id', 'id'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/ProductBundle/Action/CreateProduct.php: -------------------------------------------------------------------------------- 1 | mixin = new ProductBaseMixin($this); 19 | $this->mixin->preinit(); 20 | } 21 | 22 | public function schema() 23 | { 24 | $this->mixin->schema(); 25 | } 26 | 27 | public function successMessage($ret) 28 | { 29 | return '產品資料 ' . $this->record->name . ' 建立成功'; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /tests/ProductBundle/Action/CreateProductFile.php: -------------------------------------------------------------------------------- 1 | useRecordSchema(); 12 | 13 | $sizeLimit = 1024; // 1024kb 14 | 15 | $this->replaceParam('file','File') 16 | ->sizeLimit($sizeLimit) 17 | ->required() 18 | ->hint('product file hint') 19 | ->label('product file label') 20 | ->putIn('tests/upload') 21 | ; 22 | 23 | } 24 | 25 | 26 | } 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/ProductBundle/Action/CreateProductImage.php: -------------------------------------------------------------------------------- 1 | useRecordSchema(); 12 | 13 | $imageSizeLimit = 1024; // 1024kb 14 | $imageSize = [ 15 | 'width' => 200, 16 | 'height' => 200, 17 | ]; 18 | $autoResize = true; 19 | 20 | $this->replaceParam('image','Image') 21 | ->sizeLimit($imageSizeLimit) 22 | ->size( $imageSize ) 23 | ->autoResize($autoResize) 24 | ->sourceField( 'large' ) 25 | ->required() 26 | ->hint('product image hint') 27 | ->hintFromSizeInfo($imageSize) 28 | ->hintFromSizeLimit() 29 | ->label('product image label') 30 | ->putIn('tests/upload') 31 | ; 32 | 33 | $this->replaceParam('large','Image') 34 | ->sizeLimit($imageSizeLimit) 35 | ->size( $imageSize ) 36 | ->autoResize($autoResize) 37 | ->hint('product large image hint') 38 | ->hintFromSizeInfo() 39 | ->label('product large image label') 40 | ->putIn('tests/upload') 41 | ; 42 | 43 | } 44 | 45 | 46 | } 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tests/ProductBundle/Action/ProductBaseMixin.php: -------------------------------------------------------------------------------- 1 | object = $object; 16 | } 17 | 18 | public function preinit() 19 | { 20 | /** 21 | * TODO: Note that the self_key is pointing the related class currently. 22 | * We want to make self_key to point the action record itself. 23 | */ 24 | $this->object->nested = true; 25 | $this->object->relationships['product_categories']['renderable'] = false; 26 | } 27 | 28 | public function schema() 29 | { 30 | $this->object->useRecordSchema(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/ProductBundle/Action/UpdateProduct.php: -------------------------------------------------------------------------------- 1 | mixin = new ProductBaseMixin($this); 18 | $this->mixin->preinit(); 19 | } 20 | 21 | public function schema() 22 | { 23 | $this->mixin->schema(); 24 | } 25 | 26 | public function successMessage($ret) 27 | { 28 | return "Product {$this->record->name} updated."; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/Category.php: -------------------------------------------------------------------------------- 1 | parent_id ) 13 | return $this->parent->dataLabel() . ' > ' . $this->name; 14 | return $this->name; 15 | } 16 | 17 | public function getParent() 18 | { 19 | if( $this->parent_id ) 20 | return $this->parent; 21 | } 22 | 23 | public function getChilds() 24 | { 25 | $childs = new CategoryCollection; 26 | $childs->where(array( 'parent_id' => $this->id )); 27 | return $childs; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/CategoryCollection.php: -------------------------------------------------------------------------------- 1 | table('product_categories'); 14 | 15 | $this->column( 'name' ) 16 | ->varchar(130) 17 | ->label('產品類別名稱') 18 | ->required(1); 19 | 20 | $this->column('description') 21 | ->text() 22 | ->label('產品類別敘述') 23 | ->renderAs('TextareaInput',array( 24 | 'class' => '+=mceEditor', 25 | )); 26 | 27 | $this->column('parent_id') 28 | ->integer() 29 | ->unsigned() 30 | ->refer(CategorySchema::class) 31 | ->label( _('父類別') ) 32 | ->default(NULL) 33 | ->renderAs('SelectInput', [ 34 | 'allow_empty' => NULL, 35 | ]); 36 | 37 | // hide this category in front-end 38 | $this->column('hide') 39 | ->boolean() 40 | ->label(_('隱藏這個類別')); 41 | 42 | $this->column('thumb') 43 | ->varchar(128) 44 | ->label('縮圖') 45 | ; 46 | 47 | $this->column('image') 48 | ->varchar(128) 49 | ->label('圖片'); 50 | 51 | $this->column('handle') 52 | ->varchar(32) 53 | ->label(_('程式用操作碼')); 54 | 55 | 56 | $this->many('subcategories','ProductBundle\\Model\\CategorySchema','parent_id','id'); 57 | $this->belongsTo('parent','ProductBundle\\Model\\CategorySchema','id','parent_id'); 58 | 59 | $this->many( 'category_products', 'ProductBundle\\Model\\ProductCategorySchema', 'category_id', 'id' ); 60 | $this->manyToMany( 'products', 'category_products' , 'product'); 61 | } 62 | 63 | public function bootstrap($record) 64 | { 65 | $record->create(array('identity' => 'c1', 'name' => 'Category 1','lang' => 'en')); 66 | $record->create(array('identity' => 'c2', 'name' => 'Category 2','lang' => 'en')); 67 | $record->create(array('identity' => 'c3', 'name' => 'Category 3','lang' => 'en')); 68 | 69 | $record->create(array('name' => '類別 1', 'lang' => 'zh_TW')); 70 | $record->create(array('name' => '類別 2', 'lang' => 'zh_TW')); 71 | $record->create(array('name' => '類別 3', 'lang' => 'zh_TW')); 72 | } 73 | } 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/Feature.php: -------------------------------------------------------------------------------- 1 | name; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/FeatureCollection.php: -------------------------------------------------------------------------------- 1 | column('name')->varchar(128)->label('產品功能名稱'); 12 | $this->column('description')->text()->label( _('Description') ); 13 | $this->column('image') 14 | ->varchar(250) 15 | ->label( '產品功能圖示' ); 16 | 17 | } 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/Product.php: -------------------------------------------------------------------------------- 1 | lang ) { 17 | return '[' . _($this->lang) . '] ' . $this->name; 18 | } 19 | */ 20 | return $this->name; 21 | } 22 | 23 | public function beforeUpdate($args) { 24 | $args['updated_on'] = date('c'); 25 | return $args; 26 | } 27 | 28 | public function availableTypes() { 29 | return $this->types->filter(function($type) { 30 | return $type->quantity > 0; 31 | }); 32 | } 33 | 34 | public function renderThumb($attrs = array()) { 35 | $html = "thumb}\"" ; 36 | $attrs = array_merge(array( 37 | 'title' => $this->name, 38 | 'alt' => $this->name, 39 | ), $attrs); 40 | foreach( $attrs as $key => $val ) { 41 | $html .= " $key=\"" . addslashes($val) . "\""; 42 | } 43 | $html .= "/>"; 44 | return $html; 45 | } 46 | 47 | public function renderImage($attrs = array()) { 48 | $html = "image}\"" ; 49 | foreach( $attrs as $key => $val ) { 50 | $html .= " $key=\"" . addslashes($val) . "\""; 51 | } 52 | $html .= "/>"; 53 | return $html; 54 | } 55 | 56 | public function getPageKeywords() { } 57 | 58 | public function getPageDescription() { } 59 | 60 | public function getPageTitle() { 61 | $title = $this->name; 62 | if ($this->sn) { 63 | $title .= ' - ' . $this->sn; 64 | } 65 | return $title; 66 | } 67 | 68 | 69 | /** 70 | * @return bool check price and sellable flag. 71 | */ 72 | public function isSellable() { 73 | return $this->sellable && $this->price > 0; 74 | } 75 | 76 | 77 | protected $_allSoldOut; 78 | 79 | public function isAllSoldOut() { 80 | if ( $this->_allSoldOut !== null ) { 81 | return $this->_allSoldOut; 82 | } 83 | return $this->_allSoldOut = ! $this->types->quantityAvailable(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductCategory.php: -------------------------------------------------------------------------------- 1 | table('product_category_junction'); 14 | 15 | $this->column('product_id') 16 | ->integer() 17 | ->unsigned() 18 | ->required() 19 | ; 20 | $this->column('category_id') 21 | ->integer() 22 | ->unsigned() 23 | ->required() 24 | ; 25 | 26 | $this->belongsTo( 'category' , 'ProductBundle\\Model\\CategorySchema','id','category_id'); 27 | $this->belongsTo( 'product' , 'ProductBundle\\Model\\ProductSchema','id','product_id'); 28 | } 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductCollection.php: -------------------------------------------------------------------------------- 1 | where() 14 | ->equal('status','publish'); 15 | $items->order('created_on','desc'); 16 | return $items; 17 | } 18 | 19 | public static function getCoverProducts() { 20 | $coverProducts = new self; 21 | $coverProducts->where(array( 22 | 'is_cover' => true, 23 | 'status' => 'publish' 24 | )); 25 | $coverProducts->order('created_on','desc'); 26 | return $coverProducts; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductCollectionBase.php: -------------------------------------------------------------------------------- 1 | column('product_id')->label( _('Product Id') )->refer( 'ProductBundle\\Model\\Product' ); 13 | $this->column('feature_id')->label( _('Feature Id') )->refer( 'ProductBundle\\Model\\Feature' ); 14 | $this->belongsTo('product','ProductBundle\\Model\\ProductSchema','id','product_id'); 15 | $this->belongsTo('feature','ProductBundle\\Model\\FeatureSchema','id','feature_id'); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductFile.php: -------------------------------------------------------------------------------- 1 | column( 'product_id' ) 15 | ->integer() 16 | ->refer('ProductBundle\\Model\\Product') 17 | ->renderAs('SelectInput') 18 | ->label('產品'); 19 | 20 | $this->column( 'title' ) 21 | ->varchar(130) 22 | ->label('檔案標題'); 23 | 24 | $this->column( 'file' ) 25 | ->varchar(130) 26 | ->required() 27 | ->label('檔案'); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductImage.php: -------------------------------------------------------------------------------- 1 | column( 'product_id' ) 15 | ->integer() 16 | ->refer('ProductBundle\\Model\\Product') 17 | ->renderAs('SelectInput') 18 | ->label('產品'); 19 | 20 | $this->column( 'title' ) 21 | ->varchar(130) 22 | ->label('圖片標題'); 23 | 24 | $this->column('image') 25 | ->varchar(130) 26 | ->required() 27 | ->label('圖'); 28 | 29 | $this->column('large') 30 | ->varchar(130) 31 | ->label('最大圖'); 32 | 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductLink.php: -------------------------------------------------------------------------------- 1 | column('label')->varchar(128); 10 | $this->column('url')->varchar(128); 11 | $this->column('product_id') 12 | ->integer() 13 | ->refer( 'ProductBundle\\Model\\ProductSchema') 14 | ; 15 | $this->belongsTo('product','ProductBundle\\Model\\ProductSchema','id','product_id'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductProduct.php: -------------------------------------------------------------------------------- 1 | column('product_id') 9 | ->integer() 10 | ->refer('ProductBundle\\Model\\Product') 11 | ->renderAs('SelectInput') 12 | ->label('產品') 13 | ; 14 | $this->column('related_product_id') 15 | ->integer() 16 | ->refer('ProductBundle\\Model\\Product') 17 | ->renderAs('SelectInput') 18 | ->label('關連產品') 19 | ; 20 | 21 | 22 | $this->belongsTo('product','ProductBundle\\Model\\ProductSchema','id','product_id'); 23 | $this->belongsTo('related_product','ProductBundle\\Model\\ProductSchema','id','related_product_id'); 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductProperty.php: -------------------------------------------------------------------------------- 1 | column('name')->varchar(64); 10 | $this->column('val')->varchar(512); 11 | $this->column('product_id') 12 | ->integer() 13 | ->unsigned() 14 | ->refer(ProductSchema::class) 15 | ; 16 | $this->belongsTo('product', ProductSchema::class, 'id','product_id'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductRecipe.php: -------------------------------------------------------------------------------- 1 | column('title') 10 | ->varchar(64) 11 | ->label('子區塊標題') 12 | ->renderAs('TextInput', [ 'size' => 50 ]) 13 | ; 14 | 15 | $this->column('cover_image') 16 | ->varchar(64) 17 | ->label('子區塊封面圖') 18 | ->renderAs('ThumbImageFileInput') 19 | ; 20 | 21 | $this->column('content') 22 | ->text() 23 | ->label('子區塊內文') 24 | ->renderAs('TextareaInput', [ 25 | 'class' => '+=mceEditor', 26 | 'rows' => 5, 27 | 'cols' => 50, 28 | ]) 29 | ; 30 | 31 | $this->column('product_id') 32 | ->integer() 33 | ->refer( 'ProductBundle\\Model\\Product') 34 | ; 35 | 36 | $this->belongsTo('product','ProductBundle\\Model\\ProductSchema','id','product_id'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductType.php: -------------------------------------------------------------------------------- 1 | name; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductTypeCollection.php: -------------------------------------------------------------------------------- 1 | quantity); 14 | } 15 | return $q > 0; 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductTypeCollectionBase.php: -------------------------------------------------------------------------------- 1 | label('產品類型'); 14 | 15 | 16 | $this->column('product_id') 17 | ->integer() 18 | ->label('產品') 19 | ->renderAs('SelectInput') 20 | ->refer('ProductBundle\\Model\\ProductSchema'); 21 | 22 | $this->column('name') 23 | ->varchar(120) 24 | ->required() 25 | ->label(_('類型名稱')) 26 | ->renderAs('TextInput', [ 27 | 'size' => 20, 28 | 'placeholder' => _('如: 綠色, 黑色, 羊毛, 大、中、小等等。'), 29 | ]) 30 | ; 31 | 32 | $this->column('quantity') 33 | ->integer() 34 | ->default(0) 35 | ->label( _('數量') ) 36 | // ->renderAs('SelectInput') 37 | ->renderAs('TextInput') 38 | // ->hint(_('設定成 -1 時為不限制數量')) 39 | ->validValues(range(-1,100)) 40 | ; 41 | 42 | $this->column('comment') 43 | ->text() 44 | ->label(_('備註')) 45 | ->renderAs('TextareaInput') 46 | ; 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/ProductBundle/Model/ProductUseCase.php: -------------------------------------------------------------------------------- 1 | table('product_resources'); 10 | $this->column('product_id') 11 | ->integer() 12 | ->refer('ProductBundle\\Model\\Product') 13 | ->label('產品') 14 | ; 15 | 16 | $this->column('url') 17 | ->varchar(256) 18 | ->label( '網址' ) 19 | ; 20 | 21 | $this->column('html') 22 | ->varchar(512) 23 | ->label('內嵌 HTML') 24 | ->renderAs('TextareaInput') 25 | ; 26 | 27 | $this->belongsTo('product','ProductBundle\\Model\\ProductSchema','id','product_id'); 28 | } 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/Templates/foo.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/User/Model/User.php: -------------------------------------------------------------------------------- 1 | 'id', 53 | 1 => 'name', 54 | 2 => 'email', 55 | 3 => 'password', 56 | ); 57 | 58 | public static $mixin_classes = array ( 59 | ); 60 | 61 | protected $table = 'users'; 62 | 63 | public $id; 64 | 65 | public $name; 66 | 67 | public $email; 68 | 69 | public $password; 70 | 71 | public static function getSchema() 72 | { 73 | static $schema; 74 | if ($schema) { 75 | return $schema; 76 | } 77 | return $schema = new \User\Model\UserSchemaProxy; 78 | } 79 | 80 | public static function createRepo($write, $read) 81 | { 82 | return new \User\Model\UserRepoBase($write, $read); 83 | } 84 | 85 | public function getKeyName() 86 | { 87 | return 'id'; 88 | } 89 | 90 | public function getKey() 91 | { 92 | return $this->id; 93 | } 94 | 95 | public function hasKey() 96 | { 97 | return isset($this->id); 98 | } 99 | 100 | public function setKey($key) 101 | { 102 | return $this->id = $key; 103 | } 104 | 105 | public function removeLocalPrimaryKey() 106 | { 107 | $this->id = null; 108 | } 109 | 110 | public function getId() 111 | { 112 | return intval($this->id); 113 | } 114 | 115 | public function getName() 116 | { 117 | return $this->name; 118 | } 119 | 120 | public function getEmail() 121 | { 122 | return $this->email; 123 | } 124 | 125 | public function getPassword() 126 | { 127 | return $this->password; 128 | } 129 | 130 | public function getAlterableData() 131 | { 132 | return ["id" => $this->id, "name" => $this->name, "email" => $this->email, "password" => $this->password]; 133 | } 134 | 135 | public function getData() 136 | { 137 | return ["id" => $this->id, "name" => $this->name, "email" => $this->email, "password" => $this->password]; 138 | } 139 | 140 | public function setData(array $data) 141 | { 142 | if (array_key_exists("id", $data)) { $this->id = $data["id"]; } 143 | if (array_key_exists("name", $data)) { $this->name = $data["name"]; } 144 | if (array_key_exists("email", $data)) { $this->email = $data["email"]; } 145 | if (array_key_exists("password", $data)) { $this->password = $data["password"]; } 146 | } 147 | 148 | public function clear() 149 | { 150 | $this->id = NULL; 151 | $this->name = NULL; 152 | $this->email = NULL; 153 | $this->password = NULL; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /tests/User/Model/UserCollection.php: -------------------------------------------------------------------------------- 1 | column('name') 9 | ->varchar(30); 10 | 11 | $this->column('email') 12 | ->varchar(128); 13 | 14 | $this->column('password') 15 | ->varchar(128); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 'bootstrap' => ['tests/bootstrap.php'] ], 19 | 'schema' => [ 20 | 'auto_id' => 1, 21 | 'paths' => ['tests'], 22 | ], 23 | 'databases' => [ 24 | 'master' => [ 25 | 'dsn' => 'sqlite::memory:', 26 | 'user' => NULL, 27 | 'password' => NULL, 28 | ], 29 | ] 30 | ]); 31 | Bootstrap::setup($config); 32 | */ 33 | 34 | /* 35 | $loader = ComposerSchemaLoader::from('composer.json'); 36 | $loader->load(); 37 | 38 | $logger = new CLIFramework\Logger; 39 | // $logger->setQuiet(); 40 | $logger->info("Updating schema class files..."); 41 | $schemas = SchemaLoader::loadDeclaredSchemas(); 42 | $g = new SchemaGenerator($config, $logger); 43 | $g->setForceUpdate(true); 44 | $g->generate($schemas); 45 | */ 46 | 47 | /** 48 | * Clean up cache files 49 | */ 50 | const CACHE_DIR = 'src/Cache'; 51 | const UPLOAD_DIR = 'tests/upload'; 52 | if (file_exists(CACHE_DIR)) { 53 | futil_rmtree(CACHE_DIR); 54 | mkdir(CACHE_DIR, 0755, true); 55 | } else { 56 | mkdir(CACHE_DIR, 0755, true); 57 | } 58 | 59 | if (file_exists(UPLOAD_DIR)) { 60 | futil_rmtree(UPLOAD_DIR); 61 | mkdir(UPLOAD_DIR, 0755, true); 62 | } else { 63 | mkdir(UPLOAD_DIR, 0755, true); 64 | } 65 | 66 | /* 67 | use WebServerRunner\WebServerRunner; 68 | if (defined('WEB_SERVER_HOST') && defined('WEB_SERVER_PORT')) { 69 | $runner = new WebServerRunner(WEB_SERVER_HOST, WEB_SERVER_PORT, WEB_SERVER_DOCROOT); 70 | $runner->setVerbose(true); 71 | $runner->execute(); 72 | $runner->stopOnShutdown(); 73 | } 74 | */ 75 | -------------------------------------------------------------------------------- /tests/config/database.yml: -------------------------------------------------------------------------------- 1 | --- 2 | schema: 3 | paths: 4 | - tests 5 | databases: 6 | master: 7 | dsn: 'sqlite::memory:' 8 | query_options: { quote_table: true } 9 | mysql: 10 | dsn: 'mysql:host=localhost;dbname=testing' 11 | user: root 12 | # create database testing charset utf8; 13 | # grant all privileges on testing.* to testing@localhost identified by 'testing'; 14 | pgsql: 15 | dsn: 'pgsql:host=localhost;dbname=testing' 16 | user: postgres 17 | -------------------------------------------------------------------------------- /tests/config/mysql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | cli: 3 | bootstrap: tests/bootstrap.php 4 | schema: 5 | auto_id: true 6 | base_model: \Maghead\Runtime\Model 7 | base_collection: \Maghead\Runtime\Collection 8 | paths: 9 | - tests 10 | instance: 11 | local: 12 | dsn: 'mysql:host=localhost' 13 | user: root 14 | driver: mysql 15 | host: localhost 16 | password: null 17 | query_options: { } 18 | connection_options: 19 | 1002: 'SET NAMES utf8' 20 | databases: 21 | master: 22 | dsn: 'mysql:host=localhost;dbname=testing' 23 | host: localhost 24 | user: root 25 | driver: mysql 26 | database: testing 27 | password: null 28 | query_options: { } 29 | connection_options: 30 | 1002: 'SET NAMES utf8' 31 | node1: 32 | dsn: 'mysql:host=localhost;dbname=s1' 33 | host: localhost 34 | user: root 35 | driver: mysql 36 | database: s1 37 | password: null 38 | query_options: { } 39 | connection_options: 40 | 1002: 'SET NAMES utf8' 41 | node2: 42 | dsn: 'mysql:host=localhost;dbname=s2' 43 | host: localhost 44 | user: root 45 | driver: mysql 46 | database: s2 47 | password: null 48 | query_options: { } 49 | connection_options: 50 | 1002: 'SET NAMES utf8' 51 | node3: 52 | dsn: 'mysql:host=localhost;dbname=s3' 53 | host: localhost 54 | user: root 55 | driver: mysql 56 | database: s3 57 | password: null 58 | query_options: { } 59 | connection_options: 60 | 1002: 'SET NAMES utf8' 61 | -------------------------------------------------------------------------------- /tests/config/mysql_configserver.yml: -------------------------------------------------------------------------------- 1 | --- 2 | appId: testapp 3 | configServer: "mongodb://localhost:27017" 4 | cli: 5 | bootstrap: tests/bootstrap.php 6 | schema: 7 | auto_id: true 8 | base_model: \Maghead\Runtime\Model 9 | base_collection: \Maghead\Runtime\Collection 10 | paths: 11 | - tests 12 | instance: 13 | local: 14 | dsn: 'mysql:host=localhost' 15 | user: root 16 | driver: mysql 17 | host: localhost 18 | password: null 19 | query_options: { } 20 | connection_options: 21 | 1002: 'SET NAMES utf8' 22 | databases: 23 | master: 24 | dsn: 'mysql:host=localhost;dbname=testing' 25 | host: localhost 26 | user: root 27 | driver: mysql 28 | database: testing 29 | password: null 30 | query_options: { } 31 | connection_options: 32 | 1002: 'SET NAMES utf8' 33 | node1: 34 | dsn: 'mysql:host=localhost;dbname=s1' 35 | host: localhost 36 | user: root 37 | driver: mysql 38 | database: s1 39 | password: null 40 | query_options: { } 41 | connection_options: 42 | 1002: 'SET NAMES utf8' 43 | node2: 44 | dsn: 'mysql:host=localhost;dbname=s2' 45 | host: localhost 46 | user: root 47 | driver: mysql 48 | database: s2 49 | password: null 50 | query_options: { } 51 | connection_options: 52 | 1002: 'SET NAMES utf8' 53 | node3: 54 | dsn: 'mysql:host=localhost;dbname=s3' 55 | host: localhost 56 | user: root 57 | driver: mysql 58 | database: s3 59 | password: null 60 | query_options: { } 61 | connection_options: 62 | 1002: 'SET NAMES utf8' 63 | -------------------------------------------------------------------------------- /tests/config/pgsql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | cli: 3 | bootstrap: tests/bootstrap.php 4 | schema: 5 | auto_id: true 6 | base_model: \Maghead\Runtime\Model 7 | base_collection: \Maghead\Runtime\Collection 8 | paths: 9 | - tests 10 | instance: 11 | local: 12 | dsn: 'pgsql:host=localhost' 13 | driver: pgsql 14 | host: localhost 15 | user: postgres 16 | databases: 17 | master: 18 | dsn: 'pgsql:host=localhost;dbname=testing' 19 | driver: pgsql 20 | host: localhost 21 | user: postgres 22 | node1: 23 | driver: pgsql 24 | dsn: 'pgsql:host=localhost' 25 | user: postgres 26 | host: localhost 27 | database: s1 28 | node2: 29 | driver: pgsql 30 | dsn: 'pgsql:host=localhost' 31 | user: postgres 32 | host: localhost 33 | database: s2 34 | node3: 35 | driver: pgsql 36 | dsn: 'pgsql:host=localhost' 37 | user: postgres 38 | host: localhost 39 | database: s3 40 | -------------------------------------------------------------------------------- /tests/config/sqlite.yml: -------------------------------------------------------------------------------- 1 | --- 2 | cli: 3 | bootstrap: tests/bootstrap.php 4 | schema: 5 | auto_id: true 6 | base_model: \Maghead\Runtime\Model 7 | base_collection: \Maghead\Runtime\Collection 8 | paths: 9 | - tests 10 | instance: 11 | local: 12 | dsn: 'sqlite::memory:' 13 | databases: 14 | master: 15 | dsn: 'sqlite::memory:' 16 | query_options: { quote_table: true } 17 | node1: 18 | dsn: 'sqlite::memory:' 19 | query_options: { quote_table: true } 20 | node2: 21 | dsn: 'sqlite::memory:' 22 | query_options: { quote_table: true } 23 | node3: 24 | dsn: 'sqlite::memory:' 25 | query_options: { quote_table: true } 26 | -------------------------------------------------------------------------------- /tests/config/tmp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | cli: 3 | bootstrap: tests/bootstrap.php 4 | schema: 5 | auto_id: true 6 | base_model: \Maghead\Runtime\Model 7 | base_collection: \Maghead\Runtime\Collection 8 | paths: 9 | - tests 10 | instance: 11 | local: 12 | dsn: 'sqlite::memory:' 13 | databases: 14 | master: 15 | dsn: 'sqlite::memory:' 16 | query_options: { quote_table: true } 17 | node1: 18 | dsn: 'sqlite::memory:' 19 | query_options: { quote_table: true } 20 | node2: 21 | dsn: 'sqlite::memory:' 22 | query_options: { quote_table: true } 23 | node3: 24 | dsn: 'sqlite::memory:' 25 | query_options: { quote_table: true } 26 | -------------------------------------------------------------------------------- /tests/data/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corneltek/ActionKit/a85f04c48bdaeff4c30d35246be9545508911ada/tests/data/404.png -------------------------------------------------------------------------------- /tests/fixture/bulk_update_user4.php: -------------------------------------------------------------------------------- 1 | load('../.lazy.yml'); 6 | $config->init(); 7 | 8 | ActionKit\RecordAction\BaseRecordAction::createCRUDClass('Product\\Model\\ProductCategory', 'Create'); 9 | ActionKit\RecordAction\BaseRecordAction::createCRUDClass('Product\\Model\\ProductCategory', 'Update'); 10 | ActionKit\RecordAction\BaseRecordAction::createCRUDClass('Product\\Model\\Category', 'Create'); 11 | ActionKit\RecordAction\BaseRecordAction::createCRUDClass('Product\\Model\\Category', 'Update'); 12 | ActionKit\RecordAction\BaseRecordAction::createCRUDClass('Product\\Model\\ProductType', 'Create'); 13 | ActionKit\RecordAction\BaseRecordAction::createCRUDClass('Product\\Model\\ProductType', 'Update'); 14 | 15 | // handle actions 16 | if ( isset($_REQUEST['action']) ) { 17 | try { 18 | $container = new ActionKit\ServiceContainer; 19 | $runner = new ActionKit\ActionRunner($container); 20 | $result = $runner->run( $_REQUEST['action'] ); 21 | if ( $result && $runner->isAjax() ) { 22 | // Deprecated: 23 | // The text/plain seems work for IE8 (IE8 wraps the 24 | // content with a '
' tag.
25 |             header('Cache-Control: no-cache');
26 |             header('Content-Type: text/plain; Charset=utf-8');
27 | 
28 |             // Since we are using "textContent" instead of "innerHTML" attributes
29 |             // we should output the correct json mime type.
30 |             // header('Content-Type: application/json; Charset=utf-8');
31 |             echo $result->__toString();
32 |             exit(0);
33 |         }
34 |     } catch ( Exception $e ) {
35 |         /**
36 |             * Return 403 status forbidden
37 |             */
38 |         header('HTTP/1.0 403');
39 |         if ( $runner->isAjax() ) {
40 |             die(json_encode(array(
41 |                     'error' => 1,
42 |                     'message' => $e->getMessage()
43 |             )));
44 |         } else {
45 |             die( $e->getMessage() );
46 |         }
47 |     }
48 | }
49 | 
50 | 
51 | if ( isset($result) ) {
52 |     var_dump($result->message);
53 | }
54 | 
55 | 
56 | $class = ActionKit\RecordAction\BaseRecordAction::createCRUDClass('Product\\Model\\Product', 'Create');
57 | $create = new $class;
58 | echo $create->asView()->render();
59 | 


--------------------------------------------------------------------------------