├── .gitignore ├── themes └── typo3 │ └── admin.css ├── manifest.jsb2 ├── setup ├── CustomerClearPropertyKeyTypo3.php ├── CustomerMigratePropertyKeyTypo3.php ├── CustomerRenameGroupTypo3.php ├── unittest │ ├── CustomerAddFeusers.php │ ├── GroupAddTypo3TestData.php │ ├── CustomerAddTypo3TestData.php │ └── schema │ │ └── customer.php ├── FEUsersMigrateCountryTypo3.php ├── CustomerRemoveIndexesTypo3.php └── default │ └── schema │ └── customer.php ├── tests ├── phpunit.xml ├── bootstrap.php ├── phpunit-coverage.xml ├── Base │ ├── View │ │ ├── Helper │ │ │ ├── Response │ │ │ │ └── Typo3Test.php │ │ │ ├── Request │ │ │ │ └── Typo3Test.php │ │ │ └── Url │ │ │ │ ├── T3RouterTest.php │ │ │ │ └── Typo3Test.php │ │ └── Engine │ │ │ └── Typo3Test.php │ ├── Mail │ │ ├── Manager │ │ │ └── Typo3Test.php │ │ ├── Typo3Test.php │ │ └── Message │ │ │ └── Typo3Test.php │ ├── Password │ │ └── Typo3Test.php │ ├── Criteria │ │ └── Plugin │ │ │ ├── T3StatusTest.php │ │ │ ├── T3DateTest.php │ │ │ ├── T3DatetimeTest.php │ │ │ └── T3SalutationTest.php │ ├── Session │ │ └── Typo3Test.php │ └── Cache │ │ └── Typo3Test.php ├── TestHelper.php └── MShop │ ├── Group │ └── Manager │ │ └── Typo3Test.php │ └── Customer │ └── Manager │ ├── Lists │ ├── Type │ │ └── Typo3Test.php │ └── Typo3Test.php │ ├── Property │ ├── Type │ │ └── Typo3Test.php │ └── Typo3Test.php │ ├── Address │ └── Typo3Test.php │ └── Typo3Test.php ├── manifest.php ├── src ├── Base │ ├── View │ │ ├── Helper │ │ │ ├── Response │ │ │ │ └── Typo3.php │ │ │ ├── Url │ │ │ │ ├── T3Router.php │ │ │ │ └── Typo3.php │ │ │ └── Request │ │ │ │ └── Typo3.php │ │ └── Engine │ │ │ └── Typo3.php │ ├── Mail │ │ ├── Manager │ │ │ └── Typo3.php │ │ ├── Typo3.php │ │ └── Message │ │ │ └── Typo3.php │ ├── Criteria │ │ └── Plugin │ │ │ ├── T3Status.php │ │ │ ├── T3Date.php │ │ │ ├── T3Datetime.php │ │ │ └── T3Salutation.php │ ├── Password │ │ └── Typo3.php │ ├── Session │ │ └── Typo3.php │ └── Cache │ │ └── Typo3.php └── MShop │ ├── Customer │ └── Manager │ │ ├── Lists │ │ ├── Type │ │ │ └── Typo3.php │ │ └── Typo3.php │ │ ├── Property │ │ ├── Type │ │ │ └── Typo3.php │ │ └── Typo3.php │ │ └── Address │ │ └── Typo3.php │ └── Group │ └── Manager │ └── Typo3.php ├── composer.json ├── README.md ├── phing.xml ├── config └── mshop │ ├── group.php │ └── customer.php ├── LICENSE └── .circleci └── config.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .phpunit.result.cache 2 | .phpunit.cache 3 | coveralls.json 4 | coverage.xml 5 | *.log 6 | *.ser 7 | -------------------------------------------------------------------------------- /themes/typo3/admin.css: -------------------------------------------------------------------------------- 1 | /* 2 | * TYPO3 JQAdm theme 3 | */ 4 | 5 | html, body { 6 | font-family: Verdana, Arial, Helvetica, sans-serif; 7 | font-size: 14px; 8 | overflow: auto; 9 | height: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /manifest.jsb2: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "Aimeos TYPO3 extension", 3 | "licenseText": "LGPLv3, http://www.gnu.org/licenses/lgpl.html", 4 | "pkgs": [{ 5 | "name": "Aimeos Admin TYPO3 CSS", 6 | "file": "index-css", 7 | "isDebug": true, 8 | "fileIncludes": [{ 9 | "text": "admin.css", 10 | "path": "themes/typo3/" 11 | }] 12 | }], 13 | "resources" : [] 14 | } 15 | -------------------------------------------------------------------------------- /setup/CustomerClearPropertyKeyTypo3.php: -------------------------------------------------------------------------------- 1 | 'fe_users_property', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /setup/CustomerMigratePropertyKeyTypo3.php: -------------------------------------------------------------------------------- 1 | 'fe_users_property', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Base/ 6 | 7 | 8 | MShop/ 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /manifest.php: -------------------------------------------------------------------------------- 1 | 'ai-typo3', 12 | 'depends' => [ 13 | 'aimeos-core', 14 | ], 15 | 'include' => [ 16 | 'src', 17 | ], 18 | 'config' => [ 19 | 'config', 20 | ], 21 | 'setup' => [ 22 | 'setup', 23 | ], 24 | 'custom' => [ 25 | 'admin/jqadm' => [ 26 | 'manifest.jsb2', 27 | ], 28 | ], 29 | ]; 30 | -------------------------------------------------------------------------------- /setup/CustomerRenameGroupTypo3.php: -------------------------------------------------------------------------------- 1 | info( 'Migrate TYPO3 "customer/group" domain to "group"', 'vv' ); 23 | 24 | $this->update( 'fe_users_list' ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ../src 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Base/ 16 | 17 | 18 | MShop/ 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/Base/View/Helper/Response/Typo3Test.php: -------------------------------------------------------------------------------- 1 | object = new \Aimeos\Base\View\Helper\Response\Typo3( $view ); 21 | } 22 | 23 | 24 | protected function tearDown() : void 25 | { 26 | unset( $this->object ); 27 | } 28 | 29 | 30 | public function testTransform() 31 | { 32 | $this->assertInstanceOf( '\Aimeos\Base\View\Helper\Response\Typo3', $this->object->transform() ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /setup/unittest/CustomerAddFeusers.php: -------------------------------------------------------------------------------- 1 | info( 'Creating fe_users schema', 'vv' ); 23 | $db = $this->db( 'db-customer' ); 24 | 25 | $filepath = __DIR__ . '/schema/customer.php'; 26 | 27 | if( ( $list = include( $filepath ) ) === false ) { 28 | throw new \RuntimeException( sprintf( 'Unable to get schema from file "%1$s"', $filepath ) ); 29 | } 30 | 31 | foreach( $list['table'] ?? [] as $name => $fcn ) { 32 | $db->table( $name, $fcn ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Base/Mail/Manager/Typo3Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'Class TYPO3\\CMS\\Core\\Mail\\MailMessage not found' ); 21 | } 22 | 23 | $this->mock = $this->getMockBuilder( 'TYPO3\\CMS\\Core\\Mail\\MailMessage' ) 24 | ->disableOriginalConstructor() 25 | ->getMock(); 26 | } 27 | 28 | 29 | public function testGet() 30 | { 31 | $object = new \Aimeos\Base\Mail\Manager\Typo3( fn() => $this->mock ); 32 | $this->assertInstanceOf( \Aimeos\Base\Mail\Iface::class, $object->get( '' ) ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Base/View/Helper/Response/Typo3.php: -------------------------------------------------------------------------------- 1 | createResponse() ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Base/Mail/Manager/Typo3.php: -------------------------------------------------------------------------------- 1 | closure = $closure; 32 | } 33 | 34 | 35 | /** 36 | * Returns the mailer for the given name 37 | * 38 | * @param string|null $name Key for the mailer 39 | * @return \Aimeos\Base\Mail\Iface Mail object 40 | */ 41 | public function get( ?string $name = null ) : \Aimeos\Base\Mail\Iface 42 | { 43 | return new \Aimeos\Base\Mail\Typo3( $this->closure ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Base/Password/Typo3Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'TYPO3 password hashing not available' ); 21 | } 22 | 23 | $this->object = new \Aimeos\Base\Password\Typo3( new \TYPO3\CMS\Core\Crypto\PasswordHashing\Md5PasswordHash() ); 24 | } 25 | 26 | 27 | protected function tearDown() : void 28 | { 29 | unset( $this->object ); 30 | } 31 | 32 | 33 | public function testHash() 34 | { 35 | $this->assertStringStartsWith( '$1$', $this->object->hash( 'unittest' ) ); 36 | } 37 | 38 | 39 | public function testVerify() 40 | { 41 | $this->assertTrue( $this->object->verify( 'unittest', $this->object->hash( 'unittest' ) ) ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/MShop/Customer/Manager/Lists/Type/Typo3.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1' ); 21 | $this->object = new \Aimeos\Base\View\Helper\Request\Typo3( $view, 123, [], [], [], [], $server ); 22 | } 23 | 24 | 25 | protected function tearDown() : void 26 | { 27 | unset( $this->object ); 28 | } 29 | 30 | 31 | public function testTransform() 32 | { 33 | $this->assertInstanceOf( '\Aimeos\Base\View\Helper\Request\Typo3', $this->object->transform() ); 34 | } 35 | 36 | 37 | public function testGetClientAddress() 38 | { 39 | $this->assertEquals( '127.0.0.1', $this->object->getClientAddress() ); 40 | } 41 | 42 | 43 | public function testGetTarget() 44 | { 45 | $this->assertEquals( 123, $this->object->getTarget() ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /setup/FEUsersMigrateCountryTypo3.php: -------------------------------------------------------------------------------- 1 | db( 'db-customer' ); 34 | 35 | if( !$db->hasTable( ['fe_users', 'static_countries'] ) || !$db->hasColumn( 'fe_users', 'static_info_country' ) ) { 36 | return; 37 | } 38 | 39 | $this->info( 'Migrating "static_info_country" column in "fe_users" table', 'vv' ); 40 | 41 | $db->exec( ' 42 | UPDATE fe_users SET static_info_country = ( 43 | SELECT cn_iso_2 FROM static_countries WHERE cn_iso_3 = static_info_country 44 | ) WHERE LENGTH(static_info_country) = 3 45 | ' ); 46 | } 47 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aimeos/ai-typo3", 3 | "description": "TYPO3 adapter for Aimeos e-commerce integration", 4 | "keywords": ["aimeos", "adapter", "typo3", "cms", "shop", "e-commerce"], 5 | "homepage": "https://aimeos.org/TYPO3", 6 | "type": "aimeos-extension", 7 | "license": "LGPL-3.0-or-later", 8 | "support": { 9 | "source": "https://github.com/Aimeos/ai-typo3", 10 | "issues": "https://github.com/Aimeos/ai-typo3/issues", 11 | "forum": "https://aimeos.org/help", 12 | "wiki": "https://aimeos.org/docs" 13 | }, 14 | "prefer-stable": true, 15 | "minimum-stability": "dev", 16 | "require": { 17 | "php": "^8.0.11", 18 | "ext-fileinfo": "*", 19 | "nyholm/psr7": "~1.2", 20 | "nyholm/psr7-server": "~1.0", 21 | "aimeos/aimeos-core": "dev-master" 22 | }, 23 | "require-dev": { 24 | "typo3/cms-core": "~12.3||~13.4", 25 | "typo3/cms-extbase": "~12.3||~13.4", 26 | "phpunit/phpunit": "~10.0||~11.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Aimeos\\": "src" 31 | }, 32 | "classmap": [ 33 | "src" 34 | ] 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Aimeos\\": "tests" 39 | }, 40 | "classmap": [ 41 | "tests" 42 | ] 43 | }, 44 | "config": { 45 | "allow-plugins": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Base/Criteria/Plugin/T3Status.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'Class TYPO3\\CMS\\Core\\Mail\\MailMessage not found' ); 23 | } 24 | 25 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Core\\Mail\\MailMessage' ) 26 | ->disableOriginalConstructor() 27 | ->getMock(); 28 | 29 | $this->object = new \Aimeos\Base\Mail\Typo3( function() use ( $mock ) { return $mock; } ); 30 | $this->mock = $mock; 31 | } 32 | 33 | 34 | public function testCreate() 35 | { 36 | $result = $this->object->create( 'ISO-8859-1' ); 37 | $this->assertInstanceOf( '\\Aimeos\\Base\\Mail\\Message\\Iface', $result ); 38 | } 39 | 40 | 41 | public function testSend() 42 | { 43 | $this->mock->expects( $this->once() )->method( 'send' ); 44 | 45 | $this->object->send( $this->object->create() ); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /tests/Base/View/Engine/Typo3Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( '\TYPO3\CMS\Fluid\View\StandaloneView not available' ); 17 | } 18 | 19 | $view = $this->getMockBuilder( '\TYPO3\CMS\Fluid\View\StandaloneView' ) 20 | ->onlyMethods( ['assign', 'assignMultiple', 'render', 'setTemplatePathAndFilename', 'setPartialRootPaths', 'setLayoutRootPaths'] ) 21 | ->disableOriginalConstructor() 22 | ->getMock(); 23 | 24 | $view->expects( $this->once() )->method( 'setTemplatePathAndFilename' ); 25 | $view->expects( $this->once() )->method( 'assignMultiple' ); 26 | $view->expects( $this->once() )->method( 'assign' ); 27 | $view->expects( $this->once() )->method( 'render' )->willReturn( 'test' ); 28 | 29 | $object = new \Aimeos\Base\View\Engine\Typo3( $view ); 30 | $v = new \Aimeos\Base\View\Standard( [] ); 31 | 32 | $this->assertEquals( 'test', $object->render( $v, 'filepath', ['key' => 'value'] ) ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Base/View/Helper/Url/T3RouterTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'TYPO3 Router not available' ); 22 | } 23 | 24 | $this->view = new \Aimeos\Base\View\Standard(); 25 | } 26 | 27 | 28 | protected function tearDown() : void 29 | { 30 | unset( $this->view ); 31 | } 32 | 33 | 34 | public function testTransform() 35 | { 36 | $mock = $this->getMockBuilder( 'TYPO3\CMS\Core\Routing\RouterInterface' ) 37 | ->onlyMethods( array( 'generateUri', 'matchRequest' ) )->getMock(); 38 | 39 | $stub = $this->getMockBuilder( 'Psr\Http\Message\UriInterface' )->getMock(); 40 | 41 | $mock->expects( $this->once() )->method( 'generateUri' )->willReturn( $stub ); 42 | 43 | $object = new \Aimeos\Base\View\Helper\Url\T3Router( $this->view, $mock, [] ); 44 | 45 | $this->assertEquals( '', $object->transform() ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/MShop/Customer/Manager/Property/Typo3.php: -------------------------------------------------------------------------------- 1 | object = new \Aimeos\Base\Criteria\Plugin\T3Status(); 27 | } 28 | 29 | 30 | /** 31 | * Tears down the fixture. This method is called after a test is executed. 32 | */ 33 | protected function tearDown() : void 34 | { 35 | unset( $this->object ); 36 | } 37 | 38 | 39 | public function testTranslate() 40 | { 41 | $this->assertEquals( 0, $this->object->translate( 1 ) ); 42 | } 43 | 44 | 45 | public function testTranslateDisabled() 46 | { 47 | $this->assertEquals( 1, $this->object->translate( 0 ) ); 48 | } 49 | 50 | 51 | public function testReverse() 52 | { 53 | $this->assertEquals( 1, $this->object->reverse( 0 ) ); 54 | } 55 | 56 | 57 | public function testReverseDisabled() 58 | { 59 | $this->assertEquals( 0, $this->object->reverse( 1 ) ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /setup/unittest/GroupAddTypo3TestData.php: -------------------------------------------------------------------------------- 1 | info( 'Adding TYPO3 group test data', 'vv' ); 34 | 35 | $this->db( 'db-group' )->exec( "DELETE FROM fe_groups WHERE title LIKE 'unitgroup%'" ); 36 | 37 | $this->context()->setEditor( 'ai-typo3' ); 38 | $this->process(); 39 | } 40 | 41 | 42 | /** 43 | * Returns the manager for the current setup task 44 | * 45 | * @param string $domain Domain name of the manager 46 | * @param string $name Specific manager implemenation 47 | * @return \Aimeos\MShop\Common\Manager\Iface Manager object 48 | */ 49 | protected function getManager( string $domain, string $name = 'Standard' ) : \Aimeos\MShop\Common\Manager\Iface 50 | { 51 | if( $domain === 'group' ) { 52 | return parent::getManager( $domain, 'Typo3' ); 53 | } 54 | 55 | return parent::getManager( $domain ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/MShop/Customer/Manager/Address/Typo3.php: -------------------------------------------------------------------------------- 1 | createAttributes( [ 32 | 'customer.address.id' => [ 33 | 'label' => 'Customer address ID', 34 | 'internalcode' => 'id', 35 | 'internaldeps' => ['LEFT JOIN "fe_users_address" AS mcusad ON ( mcus."uid" = mcusad."parentid" )'], 36 | 'type' => 'int', 37 | 'public' => false, 38 | ] 39 | ] ) ); 40 | } 41 | 42 | 43 | /** 44 | * Returns the name of the used table 45 | * 46 | * @return string Table name 47 | */ 48 | protected function table() : string 49 | { 50 | return 'fe_users_address'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /setup/unittest/CustomerAddTypo3TestData.php: -------------------------------------------------------------------------------- 1 | info( 'Adding TYPO3 customer test data', 'vv' ); 34 | 35 | $this->db( 'db-customer' )->exec( "DELETE FROM fe_users WHERE email LIKE 'test%@example.com'" ); 36 | 37 | $this->context()->setEditor( 'ai-typo3' ); 38 | $this->process(); 39 | } 40 | 41 | 42 | /** 43 | * Returns the manager for the current setup task 44 | * 45 | * @param string $domain Domain name of the manager 46 | * @param string $name Specific manager implemenation 47 | * @return \Aimeos\MShop\Common\Manager\Iface Manager object 48 | */ 49 | protected function getManager( string $domain, string $name = 'Standard' ) : \Aimeos\MShop\Common\Manager\Iface 50 | { 51 | if( $domain === 'customer' ) { 52 | return parent::getManager( $domain, 'Typo3' ); 53 | } 54 | 55 | return parent::getManager( $domain ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Base/Mail/Typo3.php: -------------------------------------------------------------------------------- 1 | closure = $closure; 33 | } 34 | 35 | 36 | /** 37 | * Creates a new e-mail message object. 38 | * 39 | * @param string $charset Default charset of the message 40 | * @return \Aimeos\Base\Mail\Message\Iface E-mail message object 41 | */ 42 | public function create( string $charset = 'UTF-8' ) : \Aimeos\Base\Mail\Message\Iface 43 | { 44 | $closure = $this->closure; 45 | return new \Aimeos\Base\Mail\Message\Typo3( $closure(), $charset ); 46 | } 47 | 48 | 49 | /** 50 | * Sends the e-mail message to the mail server. 51 | * 52 | * @param \Aimeos\Base\Mail\Message\Iface $message E-mail message object 53 | */ 54 | public function send( \Aimeos\Base\Mail\Message\Iface $message ) : Iface 55 | { 56 | $message->object()->send(); 57 | return $this; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Base/View/Engine/Typo3.php: -------------------------------------------------------------------------------- 1 | view = $view; 34 | } 35 | 36 | 37 | /** 38 | * Renders the output based on the given template file name and the key/value pairs 39 | * 40 | * @param \Aimeos\Base\View\Iface $view View object 41 | * @param string $filename File name of the view template 42 | * @param array $values Associative list of key/value pairs 43 | * @return string Output generated by the template 44 | * @throws \Aimeos\Base\View\Exception If the template isn't found 45 | */ 46 | public function render( \Aimeos\Base\View\Iface $view, string $filename, array $values ) : string 47 | { 48 | $fluid = clone $this->view; 49 | $fluid->setTemplatePathAndFilename( $filename ); 50 | $fluid->assign( '_aimeos_view', $view ); 51 | $fluid->assignMultiple( $values ); 52 | 53 | return $fluid->render(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Base/Password/Typo3.php: -------------------------------------------------------------------------------- 1 | hasher = $hasher; 33 | } 34 | 35 | 36 | /** 37 | * Returns the hashed password 38 | * 39 | * @param string $password Clear text password string 40 | * @return string Hashed password 41 | */ 42 | public function hash( string $password ) : string 43 | { 44 | return $this->hasher->getHashedPassword( $password ); 45 | } 46 | 47 | 48 | /** 49 | * Verifies the password 50 | * 51 | * @param string $password Clear text password string 52 | * @param string $hash Hashed password 53 | * @return bool TRUE if password and hash match 54 | */ 55 | public function verify( string $password, string $hash ) : bool 56 | { 57 | return $this->hasher->checkPassword( $password, $hash ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Base/Criteria/Plugin/T3Salutation.php: -------------------------------------------------------------------------------- 1 | object = new \Aimeos\Base\Criteria\Plugin\T3Date(); 27 | } 28 | 29 | 30 | /** 31 | * Tears down the fixture. This method is called after a test is executed. 32 | */ 33 | protected function tearDown() : void 34 | { 35 | unset( $this->object ); 36 | } 37 | 38 | 39 | public function testTranslate() 40 | { 41 | $this->assertEquals( 86400, $this->object->translate( '1970-01-02' ) ); 42 | } 43 | 44 | 45 | public function testTranslateNull() 46 | { 47 | $this->assertEquals( 0, $this->object->translate( null ) ); 48 | } 49 | 50 | 51 | public function testTranslateNegative() 52 | { 53 | $this->assertEquals( -86400, $this->object->translate( '1969-12-31' ) ); 54 | } 55 | 56 | 57 | public function testReverse() 58 | { 59 | $this->assertEquals( '1970-01-02', $this->object->reverse( 86400 ) ); 60 | } 61 | 62 | 63 | public function testReverseZero() 64 | { 65 | $this->assertEquals( null, $this->object->reverse( 0 ) ); 66 | } 67 | 68 | 69 | public function testReverseNegative() 70 | { 71 | $this->assertEquals( '1969-12-31', $this->object->reverse( -86400 ) ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Base/Criteria/Plugin/T3DatetimeTest.php: -------------------------------------------------------------------------------- 1 | object = new \Aimeos\Base\Criteria\Plugin\T3Datetime(); 26 | } 27 | 28 | 29 | /** 30 | * Tears down the fixture. This method is called after a test is executed. 31 | */ 32 | protected function tearDown() : void 33 | { 34 | unset( $this->object ); 35 | } 36 | 37 | 38 | public function testTranslate() 39 | { 40 | $this->assertEquals( 3661, $this->object->translate( '1970-01-01 01:01:01' ) ); 41 | } 42 | 43 | 44 | public function testTranslateNull() 45 | { 46 | $this->assertEquals( 0, $this->object->translate( null ) ); 47 | } 48 | 49 | 50 | public function testTranslateNegative() 51 | { 52 | $this->assertEquals( -1, $this->object->translate( '1969-12-31 23:59:59' ) ); 53 | } 54 | 55 | 56 | public function testReverse() 57 | { 58 | $this->assertEquals( '1970-01-01 01:01:01', $this->object->reverse( 3661 ) ); 59 | } 60 | 61 | 62 | public function testReverseZero() 63 | { 64 | $this->assertEquals( null, $this->object->reverse( 0 ) ); 65 | } 66 | 67 | 68 | public function testReverseNegative() 69 | { 70 | $this->assertEquals( '1969-12-31 23:59:59', $this->object->reverse( -1 ) ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /setup/CustomerRemoveIndexesTypo3.php: -------------------------------------------------------------------------------- 1 | info( 'Remove customer indexes with siteid column first', 'vv' ); 23 | 24 | $this->db( 'db-customer' ) 25 | ->dropIndex( 'fe_users_address', 'idx_t3feuad_langid' ) 26 | ->dropIndex( 'fe_users_address', 'idx_t3feuad_sid_last_first' ) 27 | ->dropIndex( 'fe_users_address', 'idx_t3feuad_sid_post_addr1' ) 28 | ->dropIndex( 'fe_users_address', 'idx_t3feuad_sid_post_ci' ) 29 | ->dropIndex( 'fe_users_address', 'idx_t3feuad_sid_city' ) 30 | ->dropIndex( 'fe_users_address', 'idx_t3feuad_sid_email' ) 31 | ->dropIndex( 'fe_users_list', 'unq_t3feuli_pid_dm_sid_ty_rid' ) 32 | ->dropIndex( 'fe_users_list_type', 'unq_t3feulity_sid_dom_code' ) 33 | ->dropIndex( 'fe_users_list_type', 'idx_t3feulity_sid_status_pos' ) 34 | ->dropIndex( 'fe_users_list_type', 'idx_t3feulity_sid_label' ) 35 | ->dropIndex( 'fe_users_list_type', 'idx_t3feulity_sid_code' ) 36 | ->dropIndex( 'fe_users_property', 'fk_t3feupr_key_sid' ) 37 | ->dropIndex( 'fe_users_property', 'unq_t3feupr_sid_ty_lid_value' ) 38 | ->dropIndex( 'fe_users_property_type', 'unq_t3feuprty_sid_dom_code' ) 39 | ->dropIndex( 'fe_users_property_type', 'idx_t3feuprty_sid_status_pos' ) 40 | ->dropIndex( 'fe_users_property_type', 'idx_t3feuprty_sid_label' ) 41 | ->dropIndex( 'fe_users_property_type', 'idx_t3feuprty_sid_code' ) 42 | ->dropIndex( 'fe_users_property_type', 'fk_t3feupr_key_sid' ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Aimeos logo 3 | 4 | 5 | # Aimeos TYPO3 adapter 6 | 7 | [![Build Status](https://circleci.com/gh/aimeos/ai-typo3.svg?style=shield)](https://circleci.com/gh/aimeos/ai-typo3) 8 | [![Coverage Status](https://coveralls.io/repos/aimeos/ai-typo3/badge.svg?branch=master&service=github)](https://coveralls.io/github/aimeos/ai-typo3?branch=master) 9 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/aimeos/ai-typo3/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/aimeos/ai-typo3/?branch=master) 10 | [![License](https://poser.pugx.org/aimeos/ai-typo3/license.svg)](https://packagist.org/packages/aimeos/ai-typo3) 11 | 12 | The Aimeos web shop components can integrate into almost any PHP application and uses the infrastructure of the application for building URLs, caching content, configuration settings, logging messages, session handling, sending e-mails or handling translations. 13 | 14 | The ai-typo3 extension integrates Aimeos into the TYPO3 CMS and utilizes the native TYPO3 components for content caching, sending mails, session handling and generating URLs. 15 | 16 | If you are using the TYPO3 CMS, you don't have to install and set up the ai-typo3 extension yourself. Instead, use the [Aimeos TYPO3 package](https://github.com/aimeos/aimeos-typo3) to create a web shop within minutes. 17 | 18 | ## License 19 | 20 | The Aimeos ai-flow extension is licensed under the terms of the LGPLv3 license and is available for free. 21 | 22 | ## Links 23 | 24 | * [Web site](https://aimeos.org/TYPO3) 25 | * [Documentation](https://aimeos.org/docs/TYPO3) 26 | * [Help](https://aimeos.org/help/typo3-extension-f16/) 27 | * [Issue tracker](https://github.com/aimeos/ai-typo3/issues) 28 | * [Source code](https://github.com/aimeos/ai-typo3) 29 | -------------------------------------------------------------------------------- /phing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/Base/Criteria/Plugin/T3SalutationTest.php: -------------------------------------------------------------------------------- 1 | object = new \Aimeos\Base\Criteria\Plugin\T3Salutation(); 27 | } 28 | 29 | 30 | /** 31 | * Tears down the fixture. This method is called after a test is executed. 32 | */ 33 | protected function tearDown() : void 34 | { 35 | unset( $this->object ); 36 | } 37 | 38 | 39 | public function testTranslate() 40 | { 41 | $this->assertEquals( 99, $this->object->translate( '' ) ); 42 | } 43 | 44 | 45 | public function testTranslateMale() 46 | { 47 | $this->assertEquals( 0, $this->object->translate( 'mr' ) ); 48 | } 49 | 50 | 51 | public function testTranslateFemale() 52 | { 53 | $this->assertEquals( 1, $this->object->translate( 'ms' ) ); 54 | } 55 | 56 | 57 | public function testTranslateCompany() 58 | { 59 | $this->assertEquals( 10, $this->object->translate( 'company' ) ); 60 | } 61 | 62 | 63 | public function testReverse() 64 | { 65 | $this->assertEquals( '', $this->object->reverse( 99 ) ); 66 | } 67 | 68 | 69 | public function testReverseMale() 70 | { 71 | $this->assertEquals( 'mr', $this->object->reverse( 0 ) ); 72 | } 73 | 74 | 75 | public function testReverseFemale() 76 | { 77 | $this->assertEquals( 'ms', $this->object->reverse( 1 ) ); 78 | } 79 | 80 | 81 | public function testReverseCompany() 82 | { 83 | $this->assertEquals( 'company', $this->object->reverse( 10 ) ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /config/mshop/group.php: -------------------------------------------------------------------------------- 1 | array( 11 | 'typo3' => array( 12 | 'delete' => array( 13 | 'ansi' => ' 14 | DELETE FROM "fe_groups" 15 | WHERE :cond 16 | ' 17 | ), 18 | 'insert' => array( 19 | 'ansi' => ' 20 | INSERT INTO "fe_groups" ( :names 21 | "pid", "title", "description", "tstamp", "crdate" 22 | ) VALUES ( :values 23 | ?, ?, ?, ?, ? 24 | ) 25 | ' 26 | ), 27 | 'update' => array( 28 | 'ansi' => ' 29 | UPDATE "fe_groups" 30 | SET :names 31 | "pid" = ?, "title" = ?, "description" = ?, "tstamp" = ? 32 | WHERE "uid" = ? 33 | ' 34 | ), 35 | 'search' => array( 36 | 'ansi' => ' 37 | SELECT :columns 38 | FROM "fe_groups" mgro 39 | :joins 40 | WHERE mgro."deleted" = 0 AND :cond 41 | ORDER BY :order 42 | OFFSET :start ROWS FETCH NEXT :size ROWS ONLY 43 | ', 44 | 'mysql' => ' 45 | SELECT :columns 46 | FROM "fe_groups" mgro 47 | :joins 48 | WHERE mgro."deleted" = 0 AND :cond 49 | ORDER BY :order 50 | LIMIT :size OFFSET :start 51 | ', 52 | ), 53 | 'count' => array( 54 | 'ansi' => ' 55 | SELECT COUNT(*) AS "count" 56 | FROM ( 57 | SELECT mgro."uid" 58 | FROM "fe_groups" mgro 59 | :joins 60 | WHERE mgro."deleted" = 0 AND :cond 61 | OFFSET 0 ROWS FETCH NEXT 10000 ROWS ONLY 62 | ) AS list 63 | ', 64 | 'mysql' => ' 65 | SELECT COUNT(*) AS "count" 66 | FROM ( 67 | SELECT mgro."uid" 68 | FROM "fe_groups" mgro 69 | :joins 70 | WHERE mgro."deleted" = 0 AND :cond 71 | LIMIT 10000 OFFSET 0 72 | ) AS list 73 | ', 74 | ), 75 | 'newid' => array( 76 | 'db2' => 'SELECT IDENTITY_VAL_LOCAL()', 77 | 'mysql' => 'SELECT LAST_INSERT_ID()', 78 | 'oracle' => 'SELECT fe_groups_seq.CURRVAL FROM DUAL', 79 | 'pgsql' => 'SELECT lastval()', 80 | 'sqlite' => 'SELECT last_insert_rowid()', 81 | 'sqlsrv' => 'SELECT SCOPE_IDENTITY()', 82 | 'sqlanywhere' => 'SELECT @@IDENTITY', 83 | ), 84 | ), 85 | ), 86 | ); 87 | -------------------------------------------------------------------------------- /tests/TestHelper.php: -------------------------------------------------------------------------------- 1 | getIncludePaths(); 19 | $includepaths[] = get_include_path(); 20 | set_include_path( implode( PATH_SEPARATOR, $includepaths ) ); 21 | } 22 | 23 | 24 | public static function context( $site = 'unittest' ) 25 | { 26 | if( !isset( self::$context[$site] ) ) { 27 | self::$context[$site] = self::createContext( $site ); 28 | } 29 | 30 | return clone self::$context[$site]; 31 | } 32 | 33 | 34 | public static function getAimeos() 35 | { 36 | if( !isset( self::$aimeos ) ) 37 | { 38 | require_once 'Bootstrap.php'; 39 | spl_autoload_register( 'Aimeos\\Bootstrap::autoload' ); 40 | 41 | self::$aimeos = new \Aimeos\Bootstrap(); 42 | } 43 | 44 | return self::$aimeos; 45 | } 46 | 47 | 48 | /** 49 | * @param string $site 50 | */ 51 | private static function createContext( $site ) 52 | { 53 | $ctx = new \Aimeos\MShop\Context(); 54 | $mshop = self::getAimeos(); 55 | 56 | 57 | $paths = $mshop->getConfigPaths(); 58 | $paths[] = __DIR__ . DIRECTORY_SEPARATOR . 'config'; 59 | $file = __DIR__ . DIRECTORY_SEPARATOR . 'confdoc.ser'; 60 | 61 | $conf = new \Aimeos\Base\Config\PHPArray( [], $paths ); 62 | $conf = new \Aimeos\Base\Config\Decorator\Memory( $conf ); 63 | $conf = new \Aimeos\Base\Config\Decorator\Documentor( $conf, $file ); 64 | $ctx->setConfig( $conf ); 65 | 66 | $dbm = new \Aimeos\Base\DB\Manager\Standard( $conf->get( 'resource', [] ), 'PDO' ); 67 | $ctx->setDatabaseManager( $dbm ); 68 | 69 | $logger = new \Aimeos\Base\Logger\File( $site . '.log', \Aimeos\Base\Logger\Iface::DEBUG ); 70 | $ctx->setLogger( $logger ); 71 | 72 | $password = new \Aimeos\Base\Password\Standard(); 73 | $ctx->setPassword( $password ); 74 | 75 | $session = new \Aimeos\Base\Session\None(); 76 | $ctx->setSession( $session ); 77 | 78 | $localeManager = \Aimeos\MShop::create( $ctx, 'locale' ); 79 | $localeItem = $localeManager->bootstrap( $site, '', '', false ); 80 | $ctx->setLocale( $localeItem ); 81 | 82 | return $ctx->setEditor( 'ai-typo3' ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Base/Session/Typo3Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'TYPO3 BackendUserAuthentication not available' ); 23 | } 24 | 25 | $this->mock = $this->getMockBuilder( '\TYPO3\CMS\Core\Authentication\BackendUserAuthentication' ) 26 | ->onlyMethods( ['getSessionData', 'setAndSaveSessionData'] ) 27 | ->disableOriginalConstructor() 28 | ->getMock(); 29 | 30 | $this->object = new \Aimeos\Base\Session\Typo3( $this->mock ); 31 | } 32 | 33 | 34 | protected function tearDown() : void 35 | { 36 | unset( $this->object, $this->mock ); 37 | } 38 | 39 | 40 | public function testDel() 41 | { 42 | $this->mock->expects( $this->once() )->method( 'setAndSaveSessionData' ); 43 | 44 | $result = $this->object->del( 'test' ); 45 | 46 | $this->assertInstanceOf( \Aimeos\Base\Session\Iface::class, $result ); 47 | } 48 | 49 | 50 | public function testGet() 51 | { 52 | $this->mock->expects( $this->once() )->method( 'getSessionData' )->willReturn( '123456789' ); 53 | 54 | $this->assertEquals( '123456789', $this->object->get( 'test' ) ); 55 | } 56 | 57 | 58 | public function testPull() 59 | { 60 | $this->mock->expects( $this->once() )->method( 'setAndSaveSessionData' ); 61 | $this->mock->expects( $this->once() )->method( 'getSessionData' )->willReturn( '123456789' ); 62 | 63 | $this->assertEquals( '123456789', $this->object->pull( 'test' ) ); 64 | } 65 | 66 | 67 | public function testRemove() 68 | { 69 | $this->mock->expects( $this->once() )->method( 'setAndSaveSessionData' ); 70 | 71 | $result = $this->object->remove( ['test'] ); 72 | 73 | $this->assertInstanceOf( \Aimeos\Base\Session\Iface::class, $result ); 74 | } 75 | 76 | 77 | public function testSet() 78 | { 79 | $this->mock->expects( $this->once() )->method( 'setAndSaveSessionData' ); 80 | 81 | $result = $this->object->set( 'test', '234' ); 82 | 83 | $this->assertInstanceOf( \Aimeos\Base\Session\Iface::class, $result ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Base/View/Helper/Url/T3Router.php: -------------------------------------------------------------------------------- 1 | router = clone $router; 45 | $this->pageid = $pageid; 46 | $this->fixed = $fixed; 47 | } 48 | 49 | 50 | /** 51 | * Returns the URL assembled from the given arguments. 52 | * 53 | * @param string|null $target Route or page which should be the target of the link (if any) 54 | * @param string|null $controller Name of the controller which should be part of the link (if any) 55 | * @param string|null $action Name of the action which should be part of the link (if any) 56 | * @param array $params Associative list of parameters that should be part of the URL 57 | * @param array $trailing Trailing URL parts that are not relevant to identify the resource (for pretty URLs) 58 | * @param array $config Additional configuration parameter per URL 59 | * @return string Complete URL that can be used in the template 60 | */ 61 | public function transform( ?string $target = null, ?string $controller = null, ?string $action = null, 62 | array $params = [], array $trailing = [], array $config = [] ) : string 63 | { 64 | $params['controller'] = $controller !== null ? ucfirst( $controller ) : null; 65 | $params['action'] = $action; 66 | 67 | if( $params['locale'] ?? null ) { 68 | $params['L'] = $params['locale']; 69 | } 70 | 71 | $abs = !empty( $config['absoluteUri'] ) ? RouterInterface::ABSOLUTE_URL : RouterInterface::ABSOLUTE_PATH; 72 | 73 | return (string) $this->router->generateUri( $target ?: $this->pageid, ['ai' => $params] + $this->fixed, join( '/', $trailing ), $abs ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Base/Session/Typo3.php: -------------------------------------------------------------------------------- 1 | user = $user; 34 | } 35 | 36 | 37 | /** 38 | * Sets a list of key/value pairs. 39 | * 40 | * @param array $values Associative list of key/value pairs 41 | * @return \Aimeos\Base\Session\Iface Session instance for method chaining 42 | */ 43 | public function apply( array $values ) : Iface 44 | { 45 | foreach( $values as $key => $value ) { 46 | $this->user->setAndSaveSessionData( $key, $value ); 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | 53 | /** 54 | * Remove the given key from the session. 55 | * 56 | * @param string $name Key of the requested value in the session 57 | * @return \Aimeos\Base\Session\Iface Session instance for method chaining 58 | */ 59 | public function del( string $name ) : Iface 60 | { 61 | $this->user->setAndSaveSessionData( $name, null ); 62 | return $this; 63 | } 64 | 65 | 66 | /** 67 | * Returns the value of the requested session key. 68 | * 69 | * If the returned value wasn't a string, it's decoded from its serialized 70 | * representation. 71 | * 72 | * @param string $name Key of the requested value in the session 73 | * @param mixed $default Value returned if requested key isn't found 74 | * @return mixed Value associated to the requested key 75 | */ 76 | public function get( string $name, $default = null ) 77 | { 78 | if( ( $value = $this->user->getSessionData( $name ) ) !== null ) { 79 | return $value; 80 | } 81 | 82 | return $default; 83 | } 84 | 85 | 86 | /** 87 | * Remove the list of keys from the session. 88 | * 89 | * @param array $names Keys to remove from the session 90 | * @return \Aimeos\Base\Session\Iface Session instance for method chaining 91 | */ 92 | public function remove( array $names ) : Iface 93 | { 94 | foreach( $names as $name ) { 95 | $this->user->setAndSaveSessionData( $name, null ); 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | 102 | /** 103 | * Sets the value for the specified key. 104 | * 105 | * If the value isn't a string, it's encoded into a serialized representation 106 | * and decoded again when using the get() method. 107 | * 108 | * @param string $name Key to the value which should be stored in the session 109 | * @param mixed $value Value that should be associated with the given key 110 | * @return \Aimeos\Base\Session\Iface Session instance for method chaining 111 | */ 112 | public function set( string $name, $value ) : Iface 113 | { 114 | $this->user->setAndSaveSessionData( $name, $value ); 115 | return $this; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Base/Cache/Typo3Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'Class \\TYPO3\\CMS\\Core\\Cache\\Frontend\\FrontendInterface not found' ); 23 | } 24 | 25 | $this->mock = $this->getMockBuilder( 'TYPO3\CMS\Core\Cache\Frontend\FrontendInterface' )->getMock(); 26 | $this->object = new \Aimeos\Base\Cache\Typo3( $this->mock ); 27 | } 28 | 29 | 30 | protected function tearDown() : void 31 | { 32 | unset( $this->mock, $this->object ); 33 | } 34 | 35 | 36 | public function testClear() 37 | { 38 | $this->mock->expects( $this->once() )->method( 'flush' ); 39 | $this->assertTrue( $this->object->clear() ); 40 | } 41 | 42 | 43 | public function testClearWithSiteId() 44 | { 45 | $object = new \Aimeos\Base\Cache\Typo3( $this->mock ); 46 | 47 | $this->mock->expects( $this->once() )->method( 'flush' ); 48 | $this->assertTrue( $object->clear() ); 49 | } 50 | 51 | 52 | public function testDelete() 53 | { 54 | $this->mock->expects( $this->once() )->method( 'remove' )->with( $this->equalTo( 'key' ) ); 55 | $this->assertTrue( $this->object->delete( 'key' ) ); 56 | } 57 | 58 | 59 | public function testDeleteMultiple() 60 | { 61 | $this->mock->expects( $this->exactly( 2 ) )->method( 'remove' )->with( $this->equalTo( 'key' ) ); 62 | $this->assertTrue( $this->object->deleteMultiple( array( 'key', 'key' ) ) ); 63 | } 64 | 65 | 66 | public function testDeleteByTags() 67 | { 68 | $this->mock->expects( $this->exactly( 2 ) )->method( 'flushByTag' )->with( $this->equalTo( 'tag' ) ); 69 | $this->assertTrue( $this->object->deleteByTags( array( 'tag', 'tag' ) ) ); 70 | } 71 | 72 | 73 | public function testGet() 74 | { 75 | $this->mock->expects( $this->once() )->method( 'get' ) 76 | ->with( $this->equalTo( 'key' ) )->willReturn( 'value' ); 77 | 78 | $this->assertEquals( 'value', $this->object->get( 'key', 'default' ) ); 79 | } 80 | 81 | 82 | public function testGetMultiple() 83 | { 84 | $this->mock->expects( $this->exactly( 2 ) )->method( 'get' ) 85 | ->willReturn( 'value' ); 86 | 87 | $expected = array( 'key1' => 'value', 'key2' => 'value' ); 88 | $this->assertEquals( $expected, $this->object->getMultiple( array( 'key1', 'key2' ) ) ); 89 | } 90 | 91 | 92 | public function testHas() 93 | { 94 | $this->mock->expects( $this->once() )->method( 'has' )->willReturn( true ); 95 | $this->assertTrue( $this->object->has( 'key' ) ); 96 | } 97 | 98 | 99 | public function testSet() 100 | { 101 | $this->mock->expects( $this->once() )->method( 'set' ) 102 | ->with( 103 | $this->equalTo( 'key' ), $this->equalTo( 'value' ), 104 | $this->equalTo( ['tag'] ), $this->greaterThan( 0 ) 105 | ); 106 | 107 | $this->assertTrue( $this->object->set( 'key', 'value', '2100-01-01 00:00:00', ['tag'] ) ); 108 | } 109 | 110 | 111 | public function testSetMultiple() 112 | { 113 | $this->mock->expects( $this->once() )->method( 'set' ) 114 | ->with( 115 | $this->equalTo( 'key' ), $this->equalTo( 'value' ), 116 | $this->equalTo( ['tag'] ), $this->greaterThan( 0 ) 117 | ); 118 | 119 | $expires = '2100-01-01 00:00:00'; 120 | $this->assertTrue( $this->object->setMultiple( ['key' => 'value'], $expires, ['tag'] ) ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Base/View/Helper/Request/Typo3.php: -------------------------------------------------------------------------------- 1 | target = $target; 43 | $this->ip = ( isset( $server['REMOTE_ADDR'] ) ? $server['REMOTE_ADDR'] : null ); 44 | 45 | $psr7request = $this->createRequest( $files, $query, $post, $cookies, $server ); 46 | 47 | parent::__construct( $view, $psr7request ); 48 | } 49 | 50 | 51 | /** 52 | * Returns the client IP address. 53 | * 54 | * @return string Client IP address 55 | */ 56 | public function getClientAddress() : string 57 | { 58 | return $this->ip; 59 | } 60 | 61 | 62 | /** 63 | * Returns the current page or route name 64 | * 65 | * @return string|null Current page ID 66 | */ 67 | public function getTarget() : ?string 68 | { 69 | return $this->target; 70 | } 71 | 72 | 73 | /** 74 | * Creates a PSR-7 compatible request 75 | * 76 | * @param array $files List of uploaded files like in $_FILES 77 | * @param array $query List of uploaded files like in $_GET 78 | * @param array $post List of uploaded files like in $_POST 79 | * @param array $cookies List of uploaded files like in $_COOKIES 80 | * @param array $server List of uploaded files like in $_SERVER 81 | * @return \Psr\Http\Message\ServerRequestInterface PSR-7 request object 82 | */ 83 | protected function createRequest( array $files, array $query, array $post, array $cookies, array $server ) : \Psr\Http\Message\ServerRequestInterface 84 | { 85 | $psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); 86 | 87 | $creator = new \Nyholm\Psr7Server\ServerRequestCreator( 88 | $psr17Factory, // ServerRequestFactory 89 | $psr17Factory, // UriFactory 90 | $psr17Factory, // UploadedFileFactory 91 | $psr17Factory // StreamFactory 92 | ); 93 | 94 | if( !isset( $server['HTTP_HOST'] ) ) { 95 | $server['HTTP_HOST'] = 'localhost'; 96 | } 97 | 98 | if( !isset( $server['REQUEST_METHOD'] ) ) { 99 | $server['REQUEST_METHOD'] = 'GET'; 100 | } 101 | 102 | $headers = function_exists( 'getallheaders' ) ? getallheaders() : $creator::getHeadersFromServer( $server ); 103 | $body = fopen( 'php://input', 'r' ) ?: null; 104 | 105 | $request = $creator->fromArrays( $server, $headers, $cookies, $query, $post, $files, $body ); 106 | $psr7files = $request->getUploadedFiles(); 107 | 108 | if( isset( $psr7files['ai'] ) ) { 109 | $request = $request->withUploadedFiles( $psr7files['ai'] ); 110 | } 111 | 112 | return $request; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/Base/Mail/Message/Typo3Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'Class TYPO3\\CMS\\Core\\Mail\\MailMessage not found' ); 23 | } 24 | 25 | $this->mock = $this->getMockBuilder( 'TYPO3\\CMS\\Core\\Mail\\MailMessage' ) 26 | ->disableOriginalConstructor() 27 | ->getMock(); 28 | 29 | $this->object = new \Aimeos\Base\Mail\Message\Typo3( $this->mock, 'UTF-8' ); 30 | } 31 | 32 | 33 | protected function tearDown() : void 34 | { 35 | unset( $this->object, $this->mock ); 36 | } 37 | 38 | 39 | public function testFrom() 40 | { 41 | $this->mock->expects( $this->once() )->method( 'addFrom' ); 42 | 43 | $result = $this->object->from( 'a@b', 'test' ); 44 | $this->assertSame( $this->object, $result ); 45 | } 46 | 47 | 48 | public function testTo() 49 | { 50 | $this->mock->expects( $this->once() )->method( 'addTo' ); 51 | 52 | $result = $this->object->to( 'a@b', 'test' ); 53 | $this->assertSame( $this->object, $result ); 54 | } 55 | 56 | 57 | public function testCc() 58 | { 59 | $this->mock->expects( $this->once() )->method( 'addCc' ); 60 | 61 | $result = $this->object->cc( 'a@b', 'test' ); 62 | $this->assertSame( $this->object, $result ); 63 | } 64 | 65 | 66 | public function testBcc() 67 | { 68 | $this->mock->expects( $this->once() )->method( 'addBcc' ); 69 | 70 | $result = $this->object->bcc( 'a@b', 'test' ); 71 | $this->assertSame( $this->object, $result ); 72 | } 73 | 74 | 75 | public function testReplyTo() 76 | { 77 | $this->mock->expects( $this->once() )->method( 'addReplyTo' ); 78 | 79 | $result = $this->object->replyTo( 'a@b', 'test' ); 80 | $this->assertSame( $this->object, $result ); 81 | } 82 | 83 | 84 | public function testSend() 85 | { 86 | $this->mock->expects( $this->once() )->method( 'send' ); 87 | $this->assertSame( $this->object, $this->object->send() ); 88 | } 89 | 90 | 91 | public function testSender() 92 | { 93 | $this->mock->expects( $this->once() )->method( 'setSender' ); 94 | 95 | $result = $this->object->sender( 'a@b', 'test' ); 96 | $this->assertSame( $this->object, $result ); 97 | } 98 | 99 | 100 | public function testSubject() 101 | { 102 | $this->mock->expects( $this->once() )->method( 'setSubject' ) 103 | ->with( $this->stringContains( 'test' ) ); 104 | 105 | $result = $this->object->subject( 'test' ); 106 | $this->assertSame( $this->object, $result ); 107 | } 108 | 109 | 110 | public function testText() 111 | { 112 | $this->mock->expects( $this->once() )->method( 'text' ) 113 | ->with( $this->stringContains( 'test' ) ); 114 | 115 | $result = $this->object->text( 'test' ); 116 | $this->assertSame( $this->object, $result ); 117 | } 118 | 119 | 120 | public function testHtml() 121 | { 122 | $this->mock->expects( $this->once() )->method( 'html' ) 123 | ->with( $this->stringContains( 'test' ) ); 124 | 125 | $result = $this->object->html( 'test' ); 126 | $this->assertSame( $this->object, $result ); 127 | } 128 | 129 | 130 | public function testAttach() 131 | { 132 | $this->markTestIncomplete( 'Swift_Attachment::newInstance() is not testable' ); 133 | } 134 | 135 | 136 | public function testEmbed() 137 | { 138 | $this->markTestIncomplete( 'Swift_EmbeddedFile::newInstance() is not testable' ); 139 | } 140 | 141 | 142 | public function testObject() 143 | { 144 | $this->assertInstanceOf( 'TYPO3\\CMS\\Core\\Mail\\MailMessage', $this->object->object() ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /config/mshop/customer.php: -------------------------------------------------------------------------------- 1 | array( 11 | 'typo3' => array( 12 | 'aggregate' => array( 13 | 'ansi' => ' 14 | SELECT :keys, COUNT("val") AS "count" 15 | FROM ( 16 | SELECT :acols, :val AS "val" 17 | FROM "fe_users" mcus 18 | :joins 19 | WHERE :cond 20 | GROUP BY mcus.uid, :cols, :val 21 | ORDER BY mcus.uid DESC 22 | OFFSET :start ROWS FETCH NEXT :size ROWS ONLY 23 | ) AS list 24 | GROUP BY :keys 25 | ', 26 | 'mysql' => ' 27 | SELECT :keys, COUNT("val") AS "count" 28 | FROM ( 29 | SELECT :acols, :val AS "val" 30 | FROM "fe_users" mcus 31 | :joins 32 | WHERE :cond 33 | GROUP BY mcus.uid, :cols, :val 34 | ORDER BY mcus.uid DESC 35 | LIMIT :size OFFSET :start 36 | ) AS list 37 | GROUP BY :keys 38 | ' 39 | ), 40 | 'clear' => array( 41 | 'ansi' => ' 42 | DELETE FROM "fe_users" 43 | WHERE :cond AND "siteid" LIKE ? 44 | ', 45 | ), 46 | 'delete' => array( 47 | 'ansi' => ' 48 | DELETE FROM "fe_users" 49 | WHERE :cond AND ( "siteid" LIKE ? OR siteid = ? ) 50 | ', 51 | ), 52 | 'insert' => array( 53 | 'ansi' => ' 54 | INSERT INTO "fe_users" ( :names 55 | "name", "username", "gender", "company", "vatid", 56 | "title", "first_name", "last_name", "address", "zip", "city", "zone", 57 | "language", "telephone", "mobile", "email", "fax", "www", "longitude", "latitude", 58 | "date_of_birth", "disable", "password", "tstamp", "static_info_country", 59 | "usergroup", "pid", "editor", "siteid", "crdate" 60 | ) VALUES ( :values 61 | ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? 62 | ) 63 | ', 64 | ), 65 | 'update' => array( 66 | 'ansi' => ' 67 | UPDATE "fe_users" 68 | SET :names 69 | "name" = ?, "username" = ?, "gender" = ?, "company" = ?, "vatid" = ?, "title" = ?, 70 | "first_name" = ?, "last_name" = ?, "address" = ?, "zip" = ?, "city" = ?, "zone" = ?, 71 | "language" = ?, "telephone" = ?, "mobile" = ?, "email" = ?, "fax" = ?, "www" = ?, "longitude" = ?, 72 | "latitude" = ?, "date_of_birth" = ?, "disable" = ?, "password" = ?, "tstamp" = ?, 73 | "static_info_country" = ?, "usergroup" = ?, "pid" = ?, "editor" = ? 74 | WHERE ( "siteid" LIKE ? OR siteid = ? ) AND "uid" = ? 75 | ', 76 | ), 77 | 'search' => array( 78 | 'ansi' => ' 79 | SELECT :columns 80 | FROM "fe_users" as mcus 81 | :joins 82 | WHERE :cond AND mcus."deleted" = 0 83 | GROUP BY :group 84 | ORDER BY :order 85 | OFFSET :start ROWS FETCH NEXT :size ROWS ONLY 86 | ', 87 | 'mysql' => ' 88 | SELECT :columns 89 | FROM "fe_users" as mcus 90 | :joins 91 | WHERE :cond AND mcus."deleted" = 0 92 | GROUP BY :group 93 | ORDER BY :order 94 | LIMIT :size OFFSET :start 95 | ', 96 | ), 97 | 'count' => array( 98 | 'ansi' => ' 99 | SELECT COUNT(*) AS "count" 100 | FROM ( 101 | SELECT mcus."uid" 102 | FROM "fe_users" mcus 103 | :joins 104 | WHERE :cond AND mcus."deleted" = 0 105 | GROUP BY mcus."uid" 106 | OFFSET 0 ROWS FETCH NEXT 10000 ROWS ONLY 107 | ) AS list 108 | ', 109 | 'mysql' => ' 110 | SELECT COUNT(*) AS "count" 111 | FROM ( 112 | SELECT mcus."uid" 113 | FROM "fe_users" mcus 114 | :joins 115 | WHERE :cond AND mcus."deleted" = 0 116 | GROUP BY mcus."uid" 117 | LIMIT 10000 OFFSET 0 118 | ) AS list 119 | ', 120 | ), 121 | 'newid' => array( 122 | 'db2' => 'SELECT IDENTITY_VAL_LOCAL()', 123 | 'mysql' => 'SELECT LAST_INSERT_ID()', 124 | 'oracle' => 'SELECT fe_users.CURRVAL FROM DUAL', 125 | 'pgsql' => 'SELECT lastval()', 126 | 'sqlite' => 'SELECT last_insert_rowid()', 127 | 'sqlsrv' => 'SELECT SCOPE_IDENTITY()', 128 | 'sqlanywhere' => 'SELECT @@IDENTITY', 129 | ), 130 | ), 131 | ), 132 | ); 133 | -------------------------------------------------------------------------------- /tests/MShop/Group/Manager/Typo3Test.php: -------------------------------------------------------------------------------- 1 | editor = $context->editor(); 22 | 23 | $this->object = new \Aimeos\MShop\Group\Manager\Typo3( $context ); 24 | } 25 | 26 | 27 | protected function tearDown() : void 28 | { 29 | unset( $this->object ); 30 | } 31 | 32 | 33 | public function testClear() 34 | { 35 | $this->assertInstanceOf( \Aimeos\MShop\Common\Manager\Iface::class, $this->object->clear( array( -1 ) ) ); 36 | } 37 | 38 | 39 | public function testCreateItem() 40 | { 41 | $item = $this->object->create(); 42 | $this->assertInstanceOf( \Aimeos\MShop\Group\Item\Iface::class, $item ); 43 | } 44 | 45 | 46 | public function testCreateSearch() 47 | { 48 | $this->assertInstanceOf( \Aimeos\Base\Criteria\Iface::class, $this->object->filter() ); 49 | } 50 | 51 | 52 | public function testDeleteItems() 53 | { 54 | $this->assertInstanceOf( \Aimeos\MShop\Common\Manager\Iface::class, $this->object->delete( [-1] ) ); 55 | } 56 | 57 | 58 | public function testGetSearchAttributes() 59 | { 60 | foreach( $this->object->getSearchAttributes() as $attribute ) { 61 | $this->assertInstanceOf( \Aimeos\Base\Criteria\Attribute\Iface::class, $attribute ); 62 | } 63 | } 64 | 65 | 66 | public function testFindItem() 67 | { 68 | $item = $this->object->find( 'unitgroup' ); 69 | 70 | $this->assertEquals( 'unitgroup', $item->getCode() ); 71 | } 72 | 73 | 74 | public function testGetItem() 75 | { 76 | $item = $this->object->find( 'unitgroup' ); 77 | 78 | $this->assertEquals( $item, $this->object->get( $item->getId() ) ); 79 | } 80 | 81 | 82 | public function testSaveUpdateDeleteItem() 83 | { 84 | $item = $this->object->create(); 85 | $item->setCode( 'unittest-group' ); 86 | 87 | $resultSaved = $this->object->save( $item ); 88 | $itemSaved = $this->object->get( $item->getId() ); 89 | 90 | $itemExp = clone $itemSaved; 91 | $itemExp->setCode( 'unittest-group-2' ); 92 | 93 | $resultUpd = $this->object->save( $itemExp ); 94 | $itemUpd = $this->object->get( $itemExp->getId() ); 95 | 96 | $this->object->delete( $itemSaved->getId() ); 97 | 98 | 99 | $this->assertTrue( $item->getId() !== null ); 100 | $this->assertEquals( $item->getId(), $itemSaved->getId() ); 101 | $this->assertEquals( $item->getCode(), $itemSaved->getCode() ); 102 | $this->assertEquals( $item->getLabel(), $itemSaved->getLabel() ); 103 | 104 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeCreated() ); 105 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeModified() ); 106 | 107 | $this->assertEquals( $itemExp->getId(), $itemUpd->getId() ); 108 | $this->assertEquals( $itemExp->getCode(), $itemUpd->getCode() ); 109 | $this->assertEquals( $itemExp->getLabel(), $itemUpd->getLabel() ); 110 | 111 | $this->assertEquals( $itemExp->getTimeCreated(), $itemUpd->getTimeCreated() ); 112 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemUpd->getTimeModified() ); 113 | 114 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultSaved ); 115 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultUpd ); 116 | 117 | $this->expectException( \Aimeos\MShop\Exception::class ); 118 | $this->object->get( $itemSaved->getId() ); 119 | } 120 | 121 | 122 | public function testSearchItem() 123 | { 124 | $search = $this->object->filter(); 125 | $search->setConditions( $search->compare( '==', 'group.label', 'Unitgroup' ) ); 126 | $search->slice( 0, 1 ); 127 | 128 | $total = 0; 129 | $results = $this->object->search( $search, [], $total ); 130 | 131 | $this->assertEquals( 1, count( $results ) ); 132 | $this->assertEquals( 1, $total ); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/Base/View/Helper/Url/Typo3.php: -------------------------------------------------------------------------------- 1 | prefix = $uriBuilder->getArgumentPrefix(); 42 | $this->uriBuilder = clone $uriBuilder; 43 | $this->fixed = $fixed; 44 | } 45 | 46 | 47 | /** 48 | * Returns the URL assembled from the given arguments. 49 | * 50 | * @param string|null $target Route or page which should be the target of the link (if any) 51 | * @param string|null $controller Name of the controller which should be part of the link (if any) 52 | * @param string|null $action Name of the action which should be part of the link (if any) 53 | * @param array $params Associative list of parameters that should be part of the URL 54 | * @param array $trailing Trailing URL parts that are not relevant to identify the resource (for pretty URLs) 55 | * @param array $config Additional configuration parameter per URL 56 | * @return string Complete URL that can be used in the template 57 | */ 58 | public function transform( ?string $target = null, ?string $controller = null, ?string $action = null, 59 | array $params = [], array $trailing = [], array $config = [] ) : string 60 | { 61 | $locale = $this->getValue( $params, 'locale' ); 62 | 63 | if( (bool) $this->getValue( $config, 'BE', false ) === false ) { 64 | $params['controller'] = $controller !== null ? ucfirst( $controller ) : null; 65 | $params['action'] = $action; 66 | } 67 | 68 | if( $this->prefix != '' && (bool) $this->getValue( $config, 'namespace', true ) === true ) { 69 | $params = [$this->prefix => $params]; 70 | } 71 | $params += $this->fixed; 72 | 73 | if( (bool) $this->getValue( $config, 'BE', false ) === true ) { 74 | $params['controller'] = $controller !== null ? ucfirst( $controller ) : null; 75 | $params['action'] = $action; 76 | } 77 | 78 | if( ( $eid = $this->getValue( $config, 'eID' ) ) !== null ) { 79 | $params['eID'] = $eid; 80 | } 81 | 82 | if( $locale !== null && (bool) $this->getValue( $config, 'BE', false ) === false ) { 83 | $params['L'] = $locale; 84 | } 85 | 86 | $useCHash = (bool) $this->getValue( $config, 'chash', false ); 87 | 88 | $this->uriBuilder->reset() 89 | ->setCreateAbsoluteUri( (bool) $this->getValue( $config, 'absoluteUri', false ) ) 90 | ->setTargetPageType( (int) $this->getValue( $config, 'type', 0 ) ) 91 | ->setNoCache( (bool) $this->getValue( $config, 'nocache', false ) ) 92 | ->setFormat( (string) $this->getValue( $config, 'format', '' ) ) 93 | ->setArguments( $this->sanitize( $params ) ) 94 | ->setSection( join( '/', $trailing ) ); 95 | 96 | if( $target ) { 97 | $this->uriBuilder->setTargetPageUid( (int) $target ); 98 | } 99 | 100 | if( (bool) $this->getValue( $config, 'BE', false ) === true ) { 101 | return (string) $this->uriBuilder->buildBackendUri(); 102 | } 103 | 104 | $url = (string) $this->uriBuilder->buildFrontendUri(); 105 | return $useCHash ? $url : preg_replace( '/\&cHash=[0-9a-f]{32}/', '', $url ); // wokaround for bad TYPO3 behavior 106 | } 107 | 108 | 109 | /** 110 | * Returns the sanitized configuration values. 111 | * 112 | * @param array $config Associative list of key/value pairs 113 | * @param string $key Key of the value to retrieve 114 | * @param mixed $default Default value if value for key isn't found 115 | * @return mixed Configuration value for the given key or default value 116 | */ 117 | protected function getValue( array $config, string $key, $default = null ) 118 | { 119 | if( isset( $config[$key] ) ) { 120 | return $config[$key]; 121 | } 122 | 123 | return $default; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /tests/MShop/Customer/Manager/Lists/Type/Typo3Test.php: -------------------------------------------------------------------------------- 1 | editor = \TestHelper::context()->editor(); 21 | $this->object = new \Aimeos\MShop\Customer\Manager\Lists\Type\Typo3( \TestHelper::context() ); 22 | } 23 | 24 | 25 | protected function tearDown() : void 26 | { 27 | unset( $this->object ); 28 | } 29 | 30 | 31 | public function testCreateItem() 32 | { 33 | $item = $this->object->create(); 34 | $this->assertInstanceOf( \Aimeos\MShop\Type\Item\Iface::class, $item ); 35 | } 36 | 37 | 38 | public function testGetItem() 39 | { 40 | $search = $this->object->filter()->slice( 0, 1 ); 41 | $item = $this->object->search( $search )->first( new \RuntimeException( 'No list type item found' ) ); 42 | 43 | $this->assertEquals( $item, $this->object->get( $item->getId() ) ); 44 | } 45 | 46 | 47 | public function testSaveUpdateDeleteItem() 48 | { 49 | $search = $this->object->filter()->slice( 0, 1 ); 50 | $item = $this->object->search( $search )->first( new \RuntimeException( 'No list type item found' ) ); 51 | 52 | $item->setId( null ); 53 | $item->setCode( 'unitTestInit' ); 54 | $resultSaved = $this->object->save( $item ); 55 | $itemSaved = $this->object->get( $item->getId() ); 56 | 57 | $itemExp = clone $itemSaved; 58 | $itemExp->setCode( 'unitTestSave' ); 59 | $resultUpd = $this->object->save( $itemExp ); 60 | $itemUpd = $this->object->get( $itemExp->getId() ); 61 | 62 | $this->object->delete( $itemSaved->getId() ); 63 | 64 | 65 | $this->assertTrue( $item->getId() !== null ); 66 | $this->assertEquals( $item->getId(), $itemSaved->getId() ); 67 | $this->assertEquals( $item->getSiteId(), $itemSaved->getSiteId() ); 68 | $this->assertEquals( $item->getCode(), $itemSaved->getCode() ); 69 | $this->assertEquals( $item->getDomain(), $itemSaved->getDomain() ); 70 | $this->assertEquals( $item->getLabel(), $itemSaved->getLabel() ); 71 | $this->assertEquals( $item->getStatus(), $itemSaved->getStatus() ); 72 | 73 | $this->assertEquals( $this->editor, $itemSaved->editor() ); 74 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeCreated() ); 75 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeModified() ); 76 | 77 | $this->assertEquals( $itemExp->getId(), $itemUpd->getId() ); 78 | $this->assertEquals( $itemExp->getSiteId(), $itemUpd->getSiteId() ); 79 | $this->assertEquals( $itemExp->getCode(), $itemUpd->getCode() ); 80 | $this->assertEquals( $itemExp->getDomain(), $itemUpd->getDomain() ); 81 | $this->assertEquals( $itemExp->getLabel(), $itemUpd->getLabel() ); 82 | $this->assertEquals( $itemExp->getStatus(), $itemUpd->getStatus() ); 83 | 84 | $this->assertEquals( $this->editor, $itemUpd->editor() ); 85 | $this->assertEquals( $itemExp->getTimeCreated(), $itemUpd->getTimeCreated() ); 86 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemUpd->getTimeModified() ); 87 | 88 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultSaved ); 89 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultUpd ); 90 | 91 | $this->expectException( '\\Aimeos\\MShop\\Exception' ); 92 | $this->object->get( $itemSaved->getId() ); 93 | } 94 | 95 | 96 | public function testSearchItems() 97 | { 98 | $total = 0; 99 | $search = $this->object->filter(); 100 | 101 | $expr = []; 102 | $expr[] = $search->compare( '!=', 'customer.lists.type.id', 0 ); 103 | $expr[] = $search->compare( '!=', 'customer.lists.type.siteid', null ); 104 | $expr[] = $search->compare( '==', 'customer.lists.type.code', 'default' ); 105 | $expr[] = $search->compare( '==', 'customer.lists.type.domain', 'customer/lists' ); 106 | $expr[] = $search->compare( '==', 'customer.lists.type.label', 'Standard' ); 107 | $expr[] = $search->compare( '>=', 'customer.lists.type.position', 0 ); 108 | $expr[] = $search->compare( '==', 'customer.lists.type.status', 1 ); 109 | $expr[] = $search->compare( '>=', 'customer.lists.type.mtime', '1970-01-01 00:00:00' ); 110 | $expr[] = $search->compare( '>=', 'customer.lists.type.ctime', '1970-01-01 00:00:00' ); 111 | $expr[] = $search->compare( '==', 'customer.lists.type.editor', 'core' ); 112 | 113 | $search->add( $search->and( $expr ) )->slice( 0, 1 ); 114 | $results = $this->object->search( $search, [], $total ); 115 | 116 | $this->assertEquals( 1, count( $results ) ); 117 | $this->assertEquals( 1, $total ); 118 | 119 | foreach( $results as $itemId => $item ) { 120 | $this->assertEquals( $itemId, $item->getId() ); 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /setup/default/schema/customer.php: -------------------------------------------------------------------------------- 1 | array( 11 | 12 | 'fe_users_address' => function( \Aimeos\Upscheme\Schema\Table $table ) { 13 | 14 | $table->engine = 'InnoDB'; 15 | 16 | $table->id()->primary( 'pk_t3feuad_id' ); 17 | $table->string( 'siteid' ); 18 | $table->int( 'parentid' ); 19 | $table->type()->default( 'delivery' ); 20 | $table->string( 'company', 100 ); 21 | $table->string( 'vatid', 32 ); 22 | $table->string( 'salutation', 8 ); 23 | $table->string( 'title', 64 ); 24 | $table->string( 'firstname', 64 ); 25 | $table->string( 'lastname', 64 ); 26 | $table->string( 'address1', 200 ); 27 | $table->string( 'address2', 200 ); 28 | $table->string( 'address3', 200 ); 29 | $table->string( 'postal', 16 ); 30 | $table->string( 'city', 200 ); 31 | $table->string( 'state', 200 ); 32 | $table->string( 'langid', 5 )->null( true ); 33 | $table->string( 'countryid', 2 )->null( true ); 34 | $table->string( 'telephone', 32 ); 35 | $table->string( 'telefax', 32 ); 36 | $table->string( 'mobile', 32 )->default( '' ); 37 | $table->string( 'email' ); 38 | $table->string( 'website' ); 39 | $table->float( 'longitude' )->null( true ); 40 | $table->float( 'latitude' )->null( true ); 41 | $table->date( 'birthday' )->null( true ); 42 | $table->smallint( 'pos' ); 43 | $table->meta(); 44 | 45 | $table->index( ['langid', 'siteid'], 'idx_t3feuad_langid_sid' ); 46 | $table->index( ['lastname', 'firstname'], 'idx_t3feuad_last_first' ); 47 | $table->index( ['postal', 'address1'], 'idx_t3feuad_post_addr1' ); 48 | $table->index( ['postal', 'city'], 'idx_t3feuad_post_ci' ); 49 | $table->index( ['city'], 'idx_t3feuad_city' ); 50 | $table->index( ['email'], 'idx_t3feuad_email' ); 51 | 52 | $table->foreign( 'parentid', 'fe_users', 'uid', 'fk_t3feuad_pid' ); 53 | }, 54 | 55 | 'fe_users_list_type' => function( \Aimeos\Upscheme\Schema\Table $table ) { 56 | 57 | $table->engine = 'InnoDB'; 58 | 59 | $table->id()->primary( 'pk_t3feulity_id' ); 60 | $table->string( 'siteid' ); 61 | $table->string( 'domain', 32 ); 62 | $table->code(); 63 | $table->string( 'label' ); 64 | $table->i18n(); 65 | $table->int( 'pos' )->default( 0 ); 66 | $table->smallint( 'status' ); 67 | $table->meta(); 68 | 69 | $table->unique( ['domain', 'code', 'siteid'], 'unq_t3feulity_dom_code_sid' ); 70 | $table->index( ['status', 'siteid', 'pos'], 'idx_t3feulity_status_sid_pos' ); 71 | $table->index( ['label', 'siteid'], 'idx_t3feulity_label_sid' ); 72 | $table->index( ['code', 'siteid'], 'idx_t3feulity_code_sid' ); 73 | }, 74 | 75 | 'fe_users_list' => function( \Aimeos\Upscheme\Schema\Table $table ) { 76 | 77 | $table->engine = 'InnoDB'; 78 | 79 | $table->id()->primary( 'pk_t3feuli_id' ); 80 | $table->string( 'siteid' ); 81 | $table->int( 'parentid' ); 82 | $table->string( 'key', 134 )->default( '' ); 83 | $table->type( 'type' ); 84 | $table->string( 'domain', 32 ); 85 | $table->refid(); 86 | $table->startend(); 87 | $table->config(); 88 | $table->int( 'pos' ); 89 | $table->smallint( 'status' ); 90 | $table->meta(); 91 | 92 | $table->unique( ['parentid', 'domain', 'type', 'refid', 'siteid'], 'unq_t3feuli_pid_dm_ty_rid_sid' ); 93 | $table->index( ['key', 'siteid'], 'idx_t3feuli_key_sid' ); 94 | 95 | $table->foreign( 'parentid', 'fe_users', 'uid', 'fk_t3feuli_pid' ); 96 | }, 97 | 98 | 'fe_users_property_type' => function( \Aimeos\Upscheme\Schema\Table $table ) { 99 | 100 | $table->engine = 'InnoDB'; 101 | 102 | $table->id()->primary( 'pk_t3feuprty_id' ); 103 | $table->string( 'siteid' ); 104 | $table->string( 'domain', 32 ); 105 | $table->code(); 106 | $table->string( 'label' ); 107 | $table->i18n(); 108 | $table->int( 'pos' )->default( 0 ); 109 | $table->smallint( 'status' ); 110 | $table->meta(); 111 | 112 | $table->unique( ['domain', 'code', 'siteid'], 'unq_t3feuprty_dom_code_sid' ); 113 | $table->index( ['status', 'siteid', 'pos'], 'idx_t3feuprty_status_sid_pos' ); 114 | $table->index( ['label', 'siteid'], 'idx_t3feuprty_label_sid' ); 115 | $table->index( ['code', 'siteid'], 'idx_t3feuprty_code_sid' ); 116 | }, 117 | 118 | 'fe_users_property' => function( \Aimeos\Upscheme\Schema\Table $table ) { 119 | 120 | $table->engine = 'InnoDB'; 121 | 122 | $table->id()->primary( 'pk_t3feupr_id' ); 123 | $table->string( 'siteid' ); 124 | $table->int( 'parentid' ); 125 | $table->string( 'key', 255 )->default( '' ); 126 | $table->type(); 127 | $table->string( 'langid', 5 )->null( true ); 128 | $table->string( 'value' ); 129 | $table->meta(); 130 | 131 | $table->unique( ['parentid', 'type', 'langid', 'value', 'siteid'], 'unq_t3feupr_pid_ty_lid_val_sid' ); 132 | $table->index( ['key', 'siteid'], 'idx_t3feupr_key_sid' ); 133 | 134 | $table->foreign( 'parentid', 'fe_users', 'uid', 'fk_t3feupr_pid' ); 135 | }, 136 | ), 137 | ); 138 | -------------------------------------------------------------------------------- /setup/unittest/schema/customer.php: -------------------------------------------------------------------------------- 1 | array( 11 | 12 | 'fe_users' => function( \Aimeos\Upscheme\Schema\Table $table ) { 13 | 14 | $table->id( 'uid' )->unsigned( true )->primary( 'PRIMARY' ); 15 | $table->string( 'siteid' )->default( '1.' ); 16 | $table->int( 'pid' )->unsigned( true )->default( 0 ); 17 | $table->int( 'deleted' )->length( 3 )->default( 0 ); 18 | $table->int( 'hidden' )->length( 3 )->default( 0 ); 19 | $table->int( 'disable' )->unsigned( true )->length( 3 )->default( 0 ); 20 | $table->int( 'tstamp' )->unsigned( true )->default( 0 ); 21 | $table->int( 'crdate' )->unsigned( true )->default( 0 ); 22 | $table->int( 'cruser_id' )->unsigned( true )->default( 0 ); 23 | $table->string( 'lockToDomain' )->length( 50 )->default( '' ); 24 | $table->text( 'TSconfig' )->default( '' ); 25 | $table->int( 'starttime' )->unsigned( true )->length( 11 )->default( 0 ); 26 | $table->int( 'endtime' )->unsigned( true )->length( 11 )->default( 0 ); 27 | $table->string( 'usergroup' )->null( true ); 28 | $table->string( 'name' )->length( 100 )->default( '' ); 29 | $table->text( 'uc' )->null( true ); 30 | $table->string( 'username' )->length( 50 )->null( true ); 31 | $table->string( 'password' )->length( 100 )->null( true ); 32 | $table->string( 'first_name' )->length( 50 )->null( true ); 33 | $table->string( 'middle_name' )->length( 50 )->null( true ); 34 | $table->string( 'last_name' )->length( 50 )->null( true ); 35 | $table->string( 'address' )->null( true ); 36 | $table->string( 'telephone' )->length( 20 )->null( true ); 37 | $table->string( 'fax' )->length( 20 )->null( true ); 38 | $table->string( 'mobile', 32 )->default( '' ); 39 | $table->string( 'email' )->length( 80 )->null( true ); 40 | $table->string( 'title' )->length( 40 )->null( true ); 41 | $table->string( 'city' )->length( 50 )->null( true ); 42 | $table->string( 'www' )->length( 80 )->null( true ); 43 | $table->string( 'company' )->length( 80 )->null( true ); 44 | $table->string( 'vatid' )->length( 50 )->null( true ); 45 | $table->string( 'zip' )->length( 20 )->default( '' ); 46 | $table->string( 'country' )->length( 60 )->default( '' ); 47 | $table->text( 'image', 255 )->null( true ); 48 | $table->string( 'felogin_forgotHash' )->length( 80 )->null( true ); 49 | $table->string( 'tx_extbase_type' )->null( true ); 50 | $table->int( 'fe_cruser_id' )->unsigned( true )->default( 0 ); 51 | $table->int( 'lastlogin' )->unsigned( true )->default( 0 ); 52 | $table->int( 'is_online' )->unsigned( true )->default( 0 ); 53 | $table->int( 'date_of_birth' )->unsigned( true )->default( 0 ); 54 | $table->string( 'zone' )->length( 45 )->default( '' ); 55 | $table->string( 'language' )->length( 2 )->default( '' ); 56 | $table->string( 'cnum' )->length( 50 )->default( '' ); 57 | $table->string( 'token' )->length( 32 )->default( '' ); 58 | $table->int( 'gender' )->unsigned( true )->default( 99 ); 59 | $table->int( 'status' )->unsigned( true )->default( 0 ); 60 | $table->int( 'by_invitation' )->unsigned( true )->default( 0 ); 61 | $table->int( 'module_sys_dmail_html' )->unsigned( true )->default( 0 ); 62 | $table->int( 'terms_acknowledged' )->unsigned( true )->default( 0 ); 63 | $table->text( 'tx_srfeuserregister_password' )->default( '' ); 64 | $table->text( 'comments' )->default( '' ); 65 | $table->text( 'felogin_redirectPid' )->length( 0xff )->default( '' ); 66 | $table->string( 'static_info_country' )->length( 3 )->default( '' ); 67 | $table->float( 'longitude' )->null( true )->default( null ); 68 | $table->float( 'latitude' )->null( true )->default( null ); 69 | $table->date( 'vdate' )->null( true ); 70 | $table->string( 'editor' )->default( '' ); 71 | 72 | $table->index( ['pid', 'username'], 'fe_users_parent' ); 73 | $table->index( ['username'], 'fe_users_username' ); 74 | $table->index( ['is_online'], 'fe_users_is_online' ); 75 | }, 76 | 77 | 'fe_groups' => function( \Aimeos\Upscheme\Schema\Table $table ) { 78 | 79 | $table->id( 'uid' )->unsigned( true )->primary( 'PRIMARY' ); 80 | $table->int( 'pid' )->unsigned( true )->default( 0 ); 81 | $table->int( 'deleted' )->length( 3 )->default( 0 ); 82 | $table->int( 'hidden' )->length( 3 )->default( 0 ); 83 | $table->int( 'tstamp' )->unsigned( true )->default( 0 ); 84 | $table->int( 'crdate' )->unsigned( true )->default( 0 ); 85 | $table->int( 'cruser_id' )->unsigned( true )->default( 0 ); 86 | $table->string( 'title' )->length( 50 )->default( '' ); 87 | $table->string( 'lockToDomain' )->length( 50 )->default( '' ); 88 | $table->text( 'description' )->default( '' ); 89 | $table->text( 'TSconfig' )->default( '' ); 90 | $table->text( 'subgroup' )->length( 0xff )->default( '' ); 91 | $table->text( 'felogin_redirectPid' )->length( 0xff )->null( true ); 92 | $table->int( 'tx_phpunit_is_dummy_record' )->unsigned( true )->length( 1 )->default( 0 ); 93 | 94 | $table->index( ['pid', 'deleted', 'hidden'], 'fe_groups_parent' ); 95 | }, 96 | ), 97 | ); 98 | -------------------------------------------------------------------------------- /tests/MShop/Customer/Manager/Property/Type/Typo3Test.php: -------------------------------------------------------------------------------- 1 | editor = $context->editor(); 22 | 23 | $this->object = new \Aimeos\MShop\Customer\Manager\Property\Type\Typo3( $context ); 24 | } 25 | 26 | 27 | protected function tearDown() : void 28 | { 29 | unset( $this->object ); 30 | } 31 | 32 | 33 | public function testClear() 34 | { 35 | $this->assertInstanceOf( \Aimeos\MShop\Common\Manager\Iface::class, $this->object->clear( array( -1 ) ) ); 36 | } 37 | 38 | 39 | public function testCreateItem() 40 | { 41 | $this->assertInstanceOf( \Aimeos\MShop\Type\Item\Iface::class, $this->object->create() ); 42 | } 43 | 44 | 45 | public function testGetSearchAttributes() 46 | { 47 | foreach( $this->object->getSearchAttributes() as $attribute ) { 48 | $this->assertInstanceOf( '\\Aimeos\\Base\\Criteria\\Attribute\\Iface', $attribute ); 49 | } 50 | } 51 | 52 | 53 | public function testGetItem() 54 | { 55 | $search = $this->object->filter()->slice( 0, 1 ); 56 | $item = $this->object->search( $search )->first( new \RuntimeException( 'No list type item found' ) ); 57 | 58 | $this->assertEquals( $item, $this->object->get( $item->getId() ) ); 59 | } 60 | 61 | 62 | public function testSaveUpdateDeleteItem() 63 | { 64 | $search = $this->object->filter()->slice( 0, 1 ); 65 | $item = $this->object->search( $search )->first( new \RuntimeException( 'No list type item found' ) ); 66 | 67 | $item->setId( null ); 68 | $item->setCode( 'unitTestSave' ); 69 | $resultSaved = $this->object->save( $item ); 70 | $itemSaved = $this->object->get( $item->getId() ); 71 | 72 | $itemExp = clone $itemSaved; 73 | $itemExp->setCode( 'unitTestSave2' ); 74 | $resultUpd = $this->object->save( $itemExp ); 75 | $itemUpd = $this->object->get( $itemExp->getId() ); 76 | 77 | $this->object->delete( $itemSaved->getId() ); 78 | 79 | 80 | $this->assertTrue( $item->getId() !== null ); 81 | $this->assertEquals( $item->getId(), $itemSaved->getId() ); 82 | $this->assertEquals( $item->getSiteId(), $itemSaved->getSiteId() ); 83 | $this->assertEquals( $item->getCode(), $itemSaved->getCode() ); 84 | $this->assertEquals( $item->getDomain(), $itemSaved->getDomain() ); 85 | $this->assertEquals( $item->getLabel(), $itemSaved->getLabel() ); 86 | $this->assertEquals( $item->getStatus(), $itemSaved->getStatus() ); 87 | 88 | $this->assertEquals( $this->editor, $itemSaved->editor() ); 89 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeCreated() ); 90 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeModified() ); 91 | 92 | $this->assertEquals( $itemExp->getId(), $itemUpd->getId() ); 93 | $this->assertEquals( $itemExp->getSiteId(), $itemUpd->getSiteId() ); 94 | $this->assertEquals( $itemExp->getCode(), $itemUpd->getCode() ); 95 | $this->assertEquals( $itemExp->getDomain(), $itemUpd->getDomain() ); 96 | $this->assertEquals( $itemExp->getLabel(), $itemUpd->getLabel() ); 97 | $this->assertEquals( $itemExp->getStatus(), $itemUpd->getStatus() ); 98 | 99 | $this->assertEquals( $this->editor, $itemUpd->editor() ); 100 | $this->assertEquals( $itemExp->getTimeCreated(), $itemUpd->getTimeCreated() ); 101 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemUpd->getTimeModified() ); 102 | 103 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultSaved ); 104 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultUpd ); 105 | 106 | $this->expectException( '\\Aimeos\\MShop\\Exception' ); 107 | $this->object->get( $itemSaved->getId() ); 108 | } 109 | 110 | 111 | public function testSearchItems() 112 | { 113 | $total = 0; 114 | $search = $this->object->filter(); 115 | 116 | $expr = []; 117 | $expr[] = $search->compare( '!=', 'customer.property.type.id', null ); 118 | $expr[] = $search->compare( '!=', 'customer.property.type.siteid', null ); 119 | $expr[] = $search->compare( '==', 'customer.property.type.domain', 'customer/property' ); 120 | $expr[] = $search->compare( '==', 'customer.property.type.code', 'newsletter' ); 121 | $expr[] = $search->compare( '>', 'customer.property.type.label', '' ); 122 | $expr[] = $search->compare( '>=', 'customer.property.type.position', 0 ); 123 | $expr[] = $search->compare( '==', 'customer.property.type.status', 1 ); 124 | $expr[] = $search->compare( '>=', 'customer.property.type.mtime', '1970-01-01 00:00:00' ); 125 | $expr[] = $search->compare( '>=', 'customer.property.type.ctime', '1970-01-01 00:00:00' ); 126 | $expr[] = $search->compare( '==', 'customer.property.type.editor', 'core' ); 127 | 128 | $search->add( $search->and( $expr ) ); 129 | $results = $this->object->search( $search, [], $total ); 130 | 131 | $this->assertEquals( 1, count( $results ) ); 132 | 133 | 134 | $search = $this->object->filter(); 135 | $conditions = array( 136 | $search->compare( '=~', 'customer.property.type.code', 'newsletter' ), 137 | $search->compare( '==', 'customer.property.type.editor', 'core' ) 138 | ); 139 | $search->add( $search->and( $conditions ) )->slice( 0, 1 ); 140 | $items = $this->object->search( $search, [], $total ); 141 | 142 | $this->assertEquals( 1, count( $items ) ); 143 | $this->assertEquals( 1, $total ); 144 | 145 | foreach( $items as $itemId => $item ) { 146 | $this->assertEquals( $itemId, $item->getId() ); 147 | } 148 | } 149 | 150 | 151 | public function testGetSubManager() 152 | { 153 | $this->expectException( \LogicException::class ); 154 | $this->object->getSubManager( 'unknown' ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/MShop/Customer/Manager/Property/Typo3Test.php: -------------------------------------------------------------------------------- 1 | editor = $context->editor(); 22 | 23 | $this->object = new \Aimeos\MShop\Customer\Manager\Property\Typo3( $context ); 24 | } 25 | 26 | 27 | protected function tearDown() : void 28 | { 29 | unset( $this->object ); 30 | } 31 | 32 | 33 | public function testClear() 34 | { 35 | $this->assertInstanceOf( \Aimeos\MShop\Common\Manager\Iface::class, $this->object->clear( array( -1 ) ) ); 36 | } 37 | 38 | 39 | public function testCreateItem() 40 | { 41 | $item = $this->object->create(); 42 | $this->assertInstanceOf( '\\Aimeos\\MShop\\Common\\Item\\Property\\Iface', $item ); 43 | } 44 | 45 | 46 | public function testSaveUpdateDeleteItem() 47 | { 48 | $search = $this->object->filter(); 49 | $search->setConditions( $search->compare( '==', 'customer.property.editor', $this->editor ) ); 50 | 51 | if( ( $item = $this->object->search( $search )->first() ) === null ) { 52 | throw new \RuntimeException( 'No property item found' ); 53 | } 54 | 55 | $item->setId( null ); 56 | $item->setLanguageId( 'en' ); 57 | $resultSaved = $this->object->save( $item ); 58 | $itemSaved = $this->object->get( $item->getId() ); 59 | 60 | $itemExp = clone $itemSaved; 61 | $itemExp->setValue( 'unittest' ); 62 | $resultUpd = $this->object->save( $itemExp ); 63 | $itemUpd = $this->object->get( $itemExp->getId() ); 64 | 65 | $this->object->delete( $itemSaved->getId() ); 66 | 67 | $context = \TestHelper::context(); 68 | 69 | $this->assertTrue( $item->getId() !== null ); 70 | $this->assertEquals( $item->getId(), $itemSaved->getId() ); 71 | $this->assertEquals( $item->getParentId(), $itemSaved->getParentId() ); 72 | $this->assertEquals( $item->getSiteId(), $itemSaved->getSiteId() ); 73 | $this->assertEquals( $item->getType(), $itemSaved->getType() ); 74 | $this->assertEquals( $item->getLanguageId(), $itemSaved->getLanguageId() ); 75 | $this->assertEquals( $item->getValue(), $itemSaved->getValue() ); 76 | 77 | $this->assertEquals( $context->editor(), $itemSaved->editor() ); 78 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeCreated() ); 79 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeModified() ); 80 | 81 | $this->assertEquals( $itemExp->getId(), $itemUpd->getId() ); 82 | $this->assertEquals( $itemExp->getParentId(), $itemUpd->getParentId() ); 83 | $this->assertEquals( $itemExp->getSiteId(), $itemUpd->getSiteId() ); 84 | $this->assertEquals( $itemExp->getType(), $itemUpd->getType() ); 85 | $this->assertEquals( $itemExp->getLanguageId(), $itemUpd->getLanguageId() ); 86 | $this->assertEquals( $itemExp->getValue(), $itemUpd->getValue() ); 87 | 88 | $this->assertEquals( $context->editor(), $itemUpd->editor() ); 89 | $this->assertEquals( $itemExp->getTimeCreated(), $itemUpd->getTimeCreated() ); 90 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemUpd->getTimeModified() ); 91 | 92 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultSaved ); 93 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultUpd ); 94 | 95 | $this->expectException( '\\Aimeos\\MShop\\Exception' ); 96 | $this->object->get( $itemSaved->getId() ); 97 | } 98 | 99 | 100 | public function testGetItem() 101 | { 102 | $search = $this->object->filter(); 103 | $conditions = array( 104 | $search->compare( '~=', 'customer.property.value', '1' ), 105 | $search->compare( '==', 'customer.property.editor', $this->editor ) 106 | ); 107 | $search->setConditions( $search->and( $conditions ) ); 108 | 109 | if( ( $item = $this->object->search( $search )->first() ) === null ) { 110 | throw new \RuntimeException( sprintf( 'No customer property item found for value "%1$s".', '1' ) ); 111 | } 112 | 113 | $actual = $this->object->get( $item->getId() ); 114 | $this->assertEquals( $item, $actual ); 115 | } 116 | 117 | 118 | public function testGetSearchAttributes() 119 | { 120 | foreach( $this->object->getSearchAttributes() as $attribute ) { 121 | $this->assertInstanceOf( '\\Aimeos\\Base\\Criteria\\Attribute\\Iface', $attribute ); 122 | } 123 | } 124 | 125 | 126 | public function testSearchItems() 127 | { 128 | $total = 0; 129 | $search = $this->object->filter(); 130 | 131 | $expr = []; 132 | $expr[] = $search->compare( '!=', 'customer.property.id', null ); 133 | $expr[] = $search->compare( '!=', 'customer.property.parentid', null ); 134 | $expr[] = $search->compare( '!=', 'customer.property.siteid', null ); 135 | $expr[] = $search->compare( '==', 'customer.property.type', 'newsletter' ); 136 | $expr[] = $search->compare( '==', 'customer.property.languageid', null ); 137 | $expr[] = $search->compare( '==', 'customer.property.value', '1' ); 138 | $expr[] = $search->compare( '==', 'customer.property.editor', $this->editor ); 139 | 140 | $search->setConditions( $search->and( $expr ) ); 141 | $results = $this->object->search( $search, [], $total ); 142 | $this->assertEquals( 1, count( $results ) ); 143 | } 144 | 145 | 146 | public function testGetSubManager() 147 | { 148 | $this->assertInstanceOf( '\\Aimeos\\MShop\\Common\\Manager\\Iface', $this->object->getSubManager( 'type' ) ); 149 | $this->assertInstanceOf( '\\Aimeos\\MShop\\Common\\Manager\\Iface', $this->object->getSubManager( 'type', 'Standard' ) ); 150 | 151 | $this->expectException( \LogicException::class ); 152 | $this->object->getSubManager( 'unknown' ); 153 | } 154 | 155 | 156 | public function testGetSubManagerInvalidName() 157 | { 158 | $this->expectException( \LogicException::class ); 159 | $this->object->getSubManager( 'type', 'unknown' ); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/MShop/Customer/Manager/Lists/Typo3Test.php: -------------------------------------------------------------------------------- 1 | context = \TestHelper::context(); 23 | $this->editor = $this->context->editor(); 24 | $this->object = new \Aimeos\MShop\Customer\Manager\Lists\Typo3( \TestHelper::context() ); 25 | } 26 | 27 | 28 | protected function tearDown() : void 29 | { 30 | unset( $this->object ); 31 | } 32 | 33 | 34 | public function testCreateItem() 35 | { 36 | $item = $this->object->create(); 37 | $this->assertInstanceOf( '\\Aimeos\\MShop\\Common\\Item\\Lists\\Iface', $item ); 38 | } 39 | 40 | 41 | public function testGetItem() 42 | { 43 | $search = $this->object->filter()->slice( 0, 1 ); 44 | 45 | if( ( $item = $this->object->search( $search )->first() ) === null ) { 46 | throw new \RuntimeException( 'No item found' ); 47 | } 48 | 49 | $this->assertEquals( $item, $this->object->get( $item->getId() ) ); 50 | } 51 | 52 | 53 | public function testSaveUpdateDeleteItem() 54 | { 55 | $search = $this->object->filter()->slice( 0, 1 ); 56 | 57 | if( ( $item = $this->object->search( $search )->first() ) === null ) { 58 | throw new \RuntimeException( 'No item found' ); 59 | } 60 | 61 | $item->setId( null ); 62 | $item->setDomain( 'unittest' ); 63 | $resultSaved = $this->object->save( $item ); 64 | $itemSaved = $this->object->get( $item->getId() ); 65 | 66 | $itemExp = clone $itemSaved; 67 | $itemExp->setDomain( 'unittest2' ); 68 | $resultUpd = $this->object->save( $itemExp ); 69 | $itemUpd = $this->object->get( $itemExp->getId() ); 70 | 71 | $this->object->delete( $itemSaved->getId() ); 72 | 73 | 74 | $this->assertTrue( $item->getId() !== null ); 75 | $this->assertEquals( $item->getId(), $itemSaved->getId() ); 76 | $this->assertEquals( $item->getSiteId(), $itemSaved->getSiteId() ); 77 | $this->assertEquals( $item->getParentId(), $itemSaved->getParentId() ); 78 | $this->assertEquals( $item->getType(), $itemSaved->getType() ); 79 | $this->assertEquals( $item->getRefId(), $itemSaved->getRefId() ); 80 | $this->assertEquals( $item->getDomain(), $itemSaved->getDomain() ); 81 | $this->assertEquals( $item->getDateStart(), $itemSaved->getDateStart() ); 82 | $this->assertEquals( $item->getDateEnd(), $itemSaved->getDateEnd() ); 83 | $this->assertEquals( $item->getPosition(), $itemSaved->getPosition() ); 84 | $this->assertEquals( $this->editor, $itemSaved->editor() ); 85 | $this->assertStringStartsWith( date( 'Y-m-d', time() ), $itemSaved->getTimeCreated() ); 86 | $this->assertStringStartsWith( date( 'Y-m-d', time() ), $itemSaved->getTimeModified() ); 87 | 88 | $this->assertEquals( $this->editor, $itemSaved->editor() ); 89 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeCreated() ); 90 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeModified() ); 91 | 92 | $this->assertEquals( $itemExp->getId(), $itemUpd->getId() ); 93 | $this->assertEquals( $itemExp->getSiteId(), $itemUpd->getSiteId() ); 94 | $this->assertEquals( $itemExp->getParentId(), $itemUpd->getParentId() ); 95 | $this->assertEquals( $itemExp->getType(), $itemUpd->getType() ); 96 | $this->assertEquals( $itemExp->getRefId(), $itemUpd->getRefId() ); 97 | $this->assertEquals( $itemExp->getDomain(), $itemUpd->getDomain() ); 98 | $this->assertEquals( $itemExp->getDateStart(), $itemUpd->getDateStart() ); 99 | $this->assertEquals( $itemExp->getDateEnd(), $itemUpd->getDateEnd() ); 100 | $this->assertEquals( $itemExp->getPosition(), $itemUpd->getPosition() ); 101 | 102 | $this->assertEquals( $this->editor, $itemUpd->editor() ); 103 | $this->assertEquals( $itemExp->getTimeCreated(), $itemUpd->getTimeCreated() ); 104 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemUpd->getTimeModified() ); 105 | 106 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultSaved ); 107 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultUpd ); 108 | 109 | $this->expectException( '\\Aimeos\\MShop\\Exception' ); 110 | $this->object->get( $itemSaved->getId() ); 111 | } 112 | 113 | 114 | public function testSearchItems() 115 | { 116 | $total = 0; 117 | $search = $this->object->filter(); 118 | 119 | $expr = []; 120 | $expr[] = $search->compare( '!=', 'customer.lists.id', null ); 121 | $expr[] = $search->compare( '!=', 'customer.lists.siteid', null ); 122 | $expr[] = $search->compare( '!=', 'customer.lists.parentid', null ); 123 | $expr[] = $search->compare( '!=', 'customer.lists.key', null ); 124 | $expr[] = $search->compare( '==', 'customer.lists.domain', 'text' ); 125 | $expr[] = $search->compare( '==', 'customer.lists.type', 'default' ); 126 | $expr[] = $search->compare( '>', 'customer.lists.refid', 0 ); 127 | $expr[] = $search->compare( '==', 'customer.lists.datestart', '2010-01-01 00:00:00' ); 128 | $expr[] = $search->compare( '==', 'customer.lists.dateend', '2098-01-01 00:00:00' ); 129 | $expr[] = $search->compare( '!=', 'customer.lists.config', null ); 130 | $expr[] = $search->compare( '>', 'customer.lists.position', 0 ); 131 | $expr[] = $search->compare( '==', 'customer.lists.status', 1 ); 132 | $expr[] = $search->compare( '>=', 'customer.lists.mtime', '1970-01-01 00:00:00' ); 133 | $expr[] = $search->compare( '>=', 'customer.lists.ctime', '1970-01-01 00:00:00' ); 134 | $expr[] = $search->compare( '==', 'customer.lists.editor', $this->editor ); 135 | 136 | $search->setConditions( $search->and( $expr ) ); 137 | $search->slice( 0, 2 ); 138 | $results = $this->object->search( $search, [], $total ); 139 | $this->assertEquals( 2, count( $results ) ); 140 | $this->assertEquals( 3, $total ); 141 | 142 | foreach( $results as $itemId => $item ) { 143 | $this->assertEquals( $itemId, $item->getId() ); 144 | } 145 | } 146 | 147 | 148 | public function testSearchItemsAll() 149 | { 150 | //search without base criteria 151 | $search = $this->object->filter()->add( ['customer.lists.editor' => $this->editor] ); 152 | $this->assertGreaterThanOrEqual( 5, count( $this->object->search( $search ) ) ); 153 | } 154 | 155 | 156 | public function testSearchItemsBase() 157 | { 158 | //search with base criteria 159 | $search = $this->object->filter( true )->add( ['customer.lists.editor' => $this->editor] ); 160 | $this->assertGreaterThanOrEqual( 5, count( $this->object->search( $search ) ) ); 161 | } 162 | 163 | 164 | public function testGetSubManager() 165 | { 166 | $this->assertInstanceOf( '\\Aimeos\\MShop\\Common\\Manager\\Iface', $this->object->getSubManager( 'type' ) ); 167 | $this->assertInstanceOf( '\\Aimeos\\MShop\\Common\\Manager\\Iface', $this->object->getSubManager( 'type', 'Typo3' ) ); 168 | 169 | $this->expectException( \LogicException::class ); 170 | $this->object->getSubManager( 'unknown' ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Base/Cache/Typo3.php: -------------------------------------------------------------------------------- 1 | object = $cache; 36 | } 37 | 38 | 39 | /** 40 | * Removes all expired cache entries. 41 | * 42 | * @inheritDoc 43 | * 44 | * @return bool True on success and false on failure 45 | */ 46 | public function cleanup() : bool 47 | { 48 | $this->object->collectGarbage(); 49 | return true; 50 | } 51 | 52 | 53 | /** 54 | * Removes all entries for the current site from the cache. 55 | * 56 | * @inheritDoc 57 | * 58 | * @return bool True on success and false on failure 59 | */ 60 | public function clear() : bool 61 | { 62 | $this->object->flush(); 63 | return true; 64 | } 65 | 66 | 67 | /** 68 | * Removes the cache entry identified by the given key. 69 | * 70 | * @inheritDoc 71 | * 72 | * @param string $key Key string that identifies the single cache entry 73 | * @return bool True if the item was successfully removed. False if there was an error 74 | * @throws \Psr\SimpleCache\InvalidArgumentException 75 | */ 76 | public function delete( string $key ) : bool 77 | { 78 | $this->object->remove( $key ); 79 | return true; 80 | } 81 | 82 | 83 | /** 84 | * Removes the cache entries identified by the given keys. 85 | * 86 | * @inheritDoc 87 | * 88 | * @param iterable $keys List of key strings that identify the cache entries that should be removed 89 | * @return bool True if the items were successfully removed. False if there was an error. 90 | * @throws \Psr\SimpleCache\InvalidArgumentException 91 | */ 92 | public function deleteMultiple( iterable $keys ) : bool 93 | { 94 | foreach( $keys as $key ) { 95 | $this->object->remove( $key ); 96 | } 97 | 98 | return true; 99 | } 100 | 101 | 102 | /** 103 | * Removes the cache entries identified by the given tags. 104 | * 105 | * @inheritDoc 106 | * 107 | * @param iterable $tags List of tag strings that are associated to one or 108 | * more cache entries that should be removed 109 | * @return bool True if the items were successfully removed. False if there was an error. 110 | * @throws \Psr\SimpleCache\InvalidArgumentException 111 | */ 112 | public function deleteByTags( iterable $tags ) : bool 113 | { 114 | foreach( $tags as $tag ) { 115 | $this->object->flushByTag( $tag ); 116 | } 117 | 118 | return true; 119 | } 120 | 121 | 122 | /** 123 | * Returns the value of the requested cache key. 124 | * 125 | * @inheritDoc 126 | * 127 | * @param string $key Path to the requested value like product/id/123 128 | * @param mixed $default Value returned if requested key isn't found 129 | * @return mixed Value associated to the requested key. If no value for the 130 | * key is found in the cache, the given default value is returned 131 | * @throws \Psr\SimpleCache\InvalidArgumentException 132 | */ 133 | public function get( string $key, $default = null ) 134 | { 135 | if( ( $entry = $this->object->get( $key ) ) !== false ) { 136 | return $entry; 137 | } 138 | 139 | return $default; 140 | } 141 | 142 | 143 | /** 144 | * Returns the cached values for the given cache keys. 145 | * 146 | * @inheritDoc 147 | * 148 | * @param iterable $keys List of key strings for the requested cache entries 149 | * @param mixed $default Default value to return for keys that do not exist 150 | * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. 151 | * @throws \Psr\SimpleCache\InvalidArgumentException 152 | */ 153 | public function getMultiple( iterable $keys, $default = null ) : iterable 154 | { 155 | $result = []; 156 | 157 | foreach( $keys as $key ) 158 | { 159 | if( ( $entry = $this->object->get( $key ) ) !== false ) { 160 | $result[$key] = $entry; 161 | } else { 162 | $result[$key] = $default; 163 | } 164 | } 165 | 166 | return $result; 167 | } 168 | 169 | 170 | /** 171 | * Determines whether an item is present in the cache. 172 | * 173 | * @inheritDoc 174 | * 175 | * @param string $key The cache item key 176 | * @return bool True if cache entry is available, false if not 177 | * @throws \Psr\SimpleCache\InvalidArgumentException 178 | */ 179 | public function has( string $key ) : bool 180 | { 181 | return $this->object->has( $key ); 182 | } 183 | 184 | 185 | /** 186 | * Sets the value for the given key in the cache. 187 | * 188 | * @inheritDoc 189 | * 190 | * @param string $key Key string for the given value like product/id/123 191 | * @param mixed $value Value string that should be stored for the given key 192 | * @param \DateInterval|int|string|null $expires Date interval object, 193 | * date/time string in "YYYY-MM-DD HH:mm:ss" format or as integer TTL value 194 | * when the cache entry will expiry 195 | * @param iterable $tags List of tag strings that should be assoicated to the cache entry 196 | * @return bool True on success and false on failure. 197 | * @throws \Psr\SimpleCache\InvalidArgumentException 198 | */ 199 | public function set( string $key, $value, $expires = null, iterable $tags = [] ) : bool 200 | { 201 | if( $expires instanceof \DateInterval ) { 202 | $expires = date_create()->add( $expires )->getTimestamp() - time(); 203 | } elseif( is_string( $expires ) ) { 204 | $expires = date_create( $expires )->getTimestamp() - time(); 205 | } elseif( is_numeric( $expires ) ) { 206 | $expires = (int) max( min( $expires, 2147483647 ), 0 ); 207 | } 208 | 209 | $tagList = []; 210 | 211 | foreach( $tags as $tag ) { 212 | $tagList[] = $tag; 213 | } 214 | 215 | $this->object->set( $key, $value, $tagList, $expires ); 216 | return true; 217 | } 218 | 219 | 220 | /** 221 | * Adds or overwrites the given key/value pairs in the cache, which is much 222 | * more efficient than setting them one by one using the set() method. 223 | * 224 | * @inheritDoc 225 | * 226 | * @param iterable $pairs Associative list of key/value pairs. Both must be a string 227 | * @param \DateInterval|int|string|null $expires Date interval object, 228 | * date/time string in "YYYY-MM-DD HH:mm:ss" format or as integer TTL value 229 | * when the cache entry will expiry 230 | * @param iterable $tags List of tags that should be associated to the cache entries 231 | * @return bool True on success and false on failure. 232 | * @throws \Psr\SimpleCache\InvalidArgumentException 233 | */ 234 | public function setMultiple( iterable $pairs, $expires = null, iterable $tags = [] ) : bool 235 | { 236 | foreach( $pairs as $key => $value ) { 237 | $this->set( $key, $value, $expires, $tags ); 238 | } 239 | 240 | return true; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /tests/Base/View/Helper/Url/Typo3Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 'TYPO3 UriBuilder not available' ); 22 | } 23 | 24 | $this->view = new \Aimeos\Base\View\Standard(); 25 | } 26 | 27 | 28 | protected function tearDown() : void 29 | { 30 | unset( $this->view ); 31 | } 32 | 33 | 34 | public function testTransform() 35 | { 36 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 37 | ->onlyMethods( ['buildFrontendUri'] ) 38 | ->disableOriginalConstructor() 39 | ->getMock(); 40 | 41 | $mock->expects( $this->once() )->method( 'buildFrontendUri' ); 42 | 43 | $fixed = ['site' => 'unittest']; 44 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, $fixed ); 45 | 46 | $this->assertEquals( '', $object->transform() ); 47 | } 48 | 49 | 50 | public function testTransformAbsolute() 51 | { 52 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 53 | ->onlyMethods( ['setCreateAbsoluteUri', 'buildFrontendUri'] ) 54 | ->disableOriginalConstructor() 55 | ->getMock(); 56 | 57 | $mock->expects( $this->once() )->method( 'setCreateAbsoluteUri' ) 58 | ->with( $this->equalTo( true ) )->willReturn( $mock ); 59 | 60 | $mock->expects( $this->once() )->method( 'buildFrontendUri' )->willReturn( '' ); 61 | 62 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, [] ); 63 | 64 | $config = ['absoluteUri' => 1]; 65 | $this->assertEquals( '', $object->transform( null, null, null, [], [], $config ) ); 66 | } 67 | 68 | 69 | public function testTransformNocache() 70 | { 71 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 72 | ->onlyMethods( ['setNoCache', 'buildFrontendUri'] ) 73 | ->disableOriginalConstructor() 74 | ->getMock(); 75 | 76 | $mock->expects( $this->once() )->method( 'setNoCache' ) 77 | ->with( $this->equalTo( true ) )->willReturn( $mock ); 78 | 79 | $mock->expects( $this->once() )->method( 'buildFrontendUri' )->willReturn( '' ); 80 | 81 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, [] ); 82 | 83 | $config = ['nocache' => 1]; 84 | $this->assertEquals( '', $object->transform( null, null, null, [], [], $config ) ); 85 | } 86 | 87 | 88 | public function testTransformType() 89 | { 90 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 91 | ->onlyMethods( ['setTargetPageType', 'buildFrontendUri'] ) 92 | ->disableOriginalConstructor() 93 | ->getMock(); 94 | 95 | $mock->expects( $this->once() )->method( 'setTargetPageType' ) 96 | ->with( $this->equalTo( 123 ) )->willReturn( $mock ); 97 | 98 | $mock->expects( $this->once() )->method( 'buildFrontendUri' )->willReturn( '' ); 99 | 100 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, [] ); 101 | 102 | $config = ['type' => 123]; 103 | $this->assertEquals( '', $object->transform( null, null, null, [], [], $config ) ); 104 | } 105 | 106 | 107 | public function testTransformFormat() 108 | { 109 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 110 | ->onlyMethods( ['setFormat', 'buildFrontendUri'] ) 111 | ->disableOriginalConstructor() 112 | ->getMock(); 113 | 114 | $mock->expects( $this->once() )->method( 'setFormat' ) 115 | ->with( $this->equalTo( 'xml' ) )->willReturn( $mock ); 116 | 117 | $mock->expects( $this->once() )->method( 'buildFrontendUri' )->willReturn( '' ); 118 | 119 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, [] ); 120 | 121 | $config = ['format' => 'xml']; 122 | $this->assertEquals( '', $object->transform( null, null, null, [], [], $config ) ); 123 | } 124 | 125 | 126 | public function testTransformEID() 127 | { 128 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 129 | ->onlyMethods( ['setArguments', 'buildFrontendUri'] ) 130 | ->disableOriginalConstructor() 131 | ->getMock(); 132 | 133 | $param = ['eID' => 123, 'controller' => '', 'action' => null]; 134 | 135 | $mock->expects( $this->once() )->method( 'setArguments' ) 136 | ->with( $this->equalTo( $param ) )->willReturn( $mock ); 137 | 138 | $mock->expects( $this->once() )->method( 'buildFrontendUri' )->willReturn( '' ); 139 | 140 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, [] ); 141 | 142 | $config = ['eID' => 123]; 143 | $this->assertEquals( '', $object->transform( null, null, null, [], [], $config ) ); 144 | } 145 | 146 | 147 | public function testTransformLocale() 148 | { 149 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 150 | ->onlyMethods( ['setArguments', 'buildFrontendUri'] ) 151 | ->disableOriginalConstructor() 152 | ->getMock(); 153 | 154 | $param = ['L' => 'de', 'controller' => '', 'action' => null, 'locale' => 'de']; 155 | 156 | $mock->expects( $this->once() )->method( 'setArguments' ) 157 | ->with( $this->equalTo( $param ) )->willReturn( $mock ); 158 | 159 | $mock->expects( $this->once() )->method( 'buildFrontendUri' )->willReturn( '' ); 160 | 161 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, [] ); 162 | 163 | $params = ['locale' => 'de']; 164 | $this->assertEquals( '', $object->transform( null, null, null, $params ) ); 165 | } 166 | 167 | 168 | public function testTransformBackend() 169 | { 170 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 171 | ->onlyMethods( ['buildBackendUri'] ) 172 | ->disableOriginalConstructor() 173 | ->getMock(); 174 | 175 | $mock->expects( $this->once() )->method( 'buildBackendUri' ); 176 | 177 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, ['site' => 'unittest'] ); 178 | 179 | $params = ['test' => 'my/value']; 180 | $this->assertEquals( '', $object->transform( null, null, null, $params, [], ['BE' => 1] ) ); 181 | } 182 | 183 | 184 | public function testTransformParams() 185 | { 186 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 187 | ->onlyMethods( ['buildFrontendUri'] ) 188 | ->disableOriginalConstructor() 189 | ->getMock(); 190 | 191 | $mock->expects( $this->once() )->method( 'buildFrontendUri' ); 192 | 193 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, ['site' => 'unittest'] ); 194 | 195 | $params = ['test' => 'my/value']; 196 | $this->assertEquals( '', $object->transform( null, null, null, $params ) ); 197 | } 198 | 199 | 200 | public function testTransformNoNamespace() 201 | { 202 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 203 | ->onlyMethods( ['buildFrontendUri', 'getArgumentPrefix'] ) 204 | ->disableOriginalConstructor() 205 | ->getMock(); 206 | 207 | $mock->expects( $this->once() )->method( 'buildFrontendUri' ); 208 | $mock->expects( $this->once() )->method( 'getArgumentPrefix' )->willReturn( 'ai' ); 209 | 210 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, ['site' => 'unittest'] ); 211 | 212 | $params = ['test' => 'my/value']; 213 | $config = ['namespace' => false]; 214 | $this->assertEquals( '', $object->transform( null, null, null, $params, [], $config ) ); 215 | } 216 | 217 | 218 | public function testTransformUnchangedOriginalUriBuilder() 219 | { 220 | $mock = $this->getMockBuilder( 'TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder' ) 221 | ->onlyMethods( ['reset', 'buildFrontendUri'] ) 222 | ->disableOriginalConstructor() 223 | ->getMock(); 224 | 225 | $mock->expects( $this->once() )->method( 'reset' )->willReturn( $mock ); 226 | $mock->expects( $this->once() )->method( 'buildFrontendUri' )->willReturn( '' ); 227 | 228 | $object = new \Aimeos\Base\View\Helper\Url\Typo3( $this->view, $mock, [] ); 229 | 230 | $mock->expects( $this->never() )->method( 'reset' ); 231 | $this->assertEquals( '', $object->transform( null, null, null, [], [], [] ) ); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # PHP CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-php/ for more details 4 | # 5 | version: 2.1 6 | 7 | jobs: 8 | "php81-mysql": 9 | docker: 10 | - image: aimeos/ci-php:8.1 11 | - image: mysql:latest 12 | environment: 13 | MYSQL_ROOT_PASSWORD: rootpw 14 | MYSQL_DATABASE: aimeos 15 | MYSQL_USER: aimeos 16 | MYSQL_PASSWORD: aimeos 17 | steps: 18 | - checkout 19 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core 20 | - run: cd .. && mv project aimeos-core/ext/ai-typo3 && mv aimeos-core project && cd project 21 | - restore_cache: 22 | keys: 23 | - php81-{{ checksum "composer.json" }} 24 | - run: composer req --no-plugins --no-update "nyholm/psr7-server:^1.0" "nyholm/psr7:^1.2" "symfony/mailer:^6.4" "typo3/cms-core:^12.4" 25 | - run: composer update -n --no-plugins --prefer-dist 26 | - save_cache: 27 | key: php81-{{ checksum "composer.json" }} 28 | paths: [./vendor] 29 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php 30 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done 31 | - run: ./vendor/bin/phing -Ddir=ext/ai-typo3 setup testext 32 | 33 | "php82-mysql": 34 | docker: 35 | - image: aimeos/ci-php:8.2 36 | - image: mysql:latest 37 | environment: 38 | MYSQL_ROOT_PASSWORD: rootpw 39 | MYSQL_DATABASE: aimeos 40 | MYSQL_USER: aimeos 41 | MYSQL_PASSWORD: aimeos 42 | steps: 43 | - checkout 44 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core 45 | - run: cd .. && mv project aimeos-core/ext/ai-typo3 && mv aimeos-core project && cd project 46 | - restore_cache: 47 | keys: 48 | - php82-{{ checksum "composer.json" }} 49 | - run: composer req --no-plugins --no-update "nyholm/psr7-server:^1.0" "nyholm/psr7:^1.2" "symfony/mailer:^7.0" "typo3/cms-core:^12.4" 50 | - run: composer update -n --no-plugins --prefer-dist 51 | - save_cache: 52 | key: php82-{{ checksum "composer.json" }} 53 | paths: [./vendor] 54 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php 55 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done 56 | - run: ./vendor/bin/phing -Ddir=ext/ai-typo3 setup coverageext 57 | - run: ./vendor/bin/php-coveralls --root_dir ext/ai-typo3 -o coveralls.json -vvv -x tests/coverage.xml 58 | 59 | "php83-mysql": 60 | docker: 61 | - image: aimeos/ci-php:8.3 62 | - image: mysql:latest 63 | environment: 64 | MYSQL_ROOT_PASSWORD: rootpw 65 | MYSQL_DATABASE: aimeos 66 | MYSQL_USER: aimeos 67 | MYSQL_PASSWORD: aimeos 68 | steps: 69 | - checkout 70 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core 71 | - run: cd .. && mv project aimeos-core/ext/ai-typo3 && mv aimeos-core project && cd project 72 | - restore_cache: 73 | keys: 74 | - php83-{{ checksum "composer.json" }} 75 | - run: composer req --no-plugins --no-update "nyholm/psr7-server:^1.0" "nyholm/psr7:^1.2" "symfony/mailer:^7.0" "typo3/cms-core:^12.4" 76 | - run: composer update -n --no-plugins --prefer-dist 77 | - save_cache: 78 | key: php83-{{ checksum "composer.json" }} 79 | paths: [./vendor] 80 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php 81 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done 82 | - run: ./vendor/bin/phing -Ddir=ext/ai-typo3 setup testext 83 | 84 | "php84-mysql": 85 | docker: 86 | - image: aimeos/ci-php:8.4 87 | - image: mysql:latest 88 | environment: 89 | MYSQL_ROOT_PASSWORD: rootpw 90 | MYSQL_DATABASE: aimeos 91 | MYSQL_USER: aimeos 92 | MYSQL_PASSWORD: aimeos 93 | steps: 94 | - checkout 95 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core 96 | - run: cd .. && mv project aimeos-core/ext/ai-typo3 && mv aimeos-core project && cd project 97 | - restore_cache: 98 | keys: 99 | - php84-{{ checksum "composer.json" }} 100 | - run: composer req --no-plugins --no-update "nyholm/psr7-server:^1.0" "nyholm/psr7:^1.2" "symfony/mailer:^7.0" "typo3/cms-core:^12.4" 101 | - run: composer update -n --no-plugins --prefer-dist 102 | - save_cache: 103 | key: php84-{{ checksum "composer.json" }} 104 | paths: [./vendor] 105 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php 106 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done 107 | - run: ./vendor/bin/phing -Ddir=ext/ai-typo3 setup testext 108 | 109 | "php85-mysql": 110 | docker: 111 | - image: aimeos/ci-php:8.5 112 | - image: mysql:latest 113 | environment: 114 | MYSQL_ROOT_PASSWORD: rootpw 115 | MYSQL_DATABASE: aimeos 116 | MYSQL_USER: aimeos 117 | MYSQL_PASSWORD: aimeos 118 | steps: 119 | - checkout 120 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core 121 | - run: cd .. && mv project aimeos-core/ext/ai-typo3 && mv aimeos-core project && cd project 122 | - restore_cache: 123 | keys: 124 | - php85-{{ checksum "composer.json" }} 125 | - run: composer req --no-plugins --no-update "nyholm/psr7-server:^1.0" "nyholm/psr7:^1.2" "symfony/mailer:^7.0" "typo3/cms-core:^12.4" 126 | - run: composer update -n --no-plugins --prefer-dist 127 | - save_cache: 128 | key: php85-{{ checksum "composer.json" }} 129 | paths: [./vendor] 130 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php 131 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done 132 | - run: ./vendor/bin/phing -Ddir=ext/ai-typo3 setup testext 133 | 134 | workflows: 135 | version: 2 136 | unittest: 137 | jobs: 138 | - "php81-mysql" 139 | - "php82-mysql" 140 | - "php83-mysql" 141 | - "php84-mysql" 142 | - "php85-mysql" -------------------------------------------------------------------------------- /tests/MShop/Customer/Manager/Address/Typo3Test.php: -------------------------------------------------------------------------------- 1 | editor = \TestHelper::context()->editor(); 21 | $this->object = new \Aimeos\MShop\Customer\Manager\Address\Typo3( \TestHelper::context() ); 22 | } 23 | 24 | 25 | protected function tearDown() : void 26 | { 27 | unset( $this->object ); 28 | } 29 | 30 | 31 | public function testGetSearchAttributes() 32 | { 33 | foreach( $this->object->getSearchAttributes() as $attribute ) 34 | { 35 | $this->assertInstanceOf( '\\Aimeos\\Base\\Criteria\\Attribute\\Iface', $attribute ); 36 | } 37 | } 38 | 39 | 40 | public function testCreateItem() 41 | { 42 | $this->assertInstanceOf( \Aimeos\MShop\Customer\Item\Address\Iface::class, $this->object->create() ); 43 | } 44 | 45 | 46 | public function testGetItem() 47 | { 48 | $search = $this->object->filter()->slice( 0, 1 ); 49 | $search->setConditions( $search->compare( '~=', 'customer.address.company', 'Example company' ) ); 50 | 51 | if( ( $item = $this->object->search( $search )->first() ) === null ) { 52 | throw new \RuntimeException( 'No address item found' ); 53 | } 54 | 55 | $this->assertEquals( $item, $this->object->get( $item->getId() ) ); 56 | } 57 | 58 | 59 | public function testSaveUpdateDeleteItem() 60 | { 61 | $search = $this->object->filter()->slice( 0, 1 ); 62 | $search->setConditions( $search->compare( '~=', 'customer.address.company', 'Example company' ) ); 63 | 64 | if( ( $item = $this->object->search( $search )->first() ) === null ) { 65 | throw new \RuntimeException( 'No address item found' ); 66 | } 67 | 68 | $item->setId( null ); 69 | $resultSaved = $this->object->save( $item ); 70 | $itemSaved = $this->object->get( $item->getId() ); 71 | 72 | $itemExp = clone $itemSaved; 73 | $itemExp->setCompany( 'unitTest' ); 74 | $resultUpd = $this->object->save( $itemExp ); 75 | $itemUpd = $this->object->get( $itemExp->getId() ); 76 | 77 | $this->object->delete( $item->getId() ); 78 | 79 | 80 | $this->assertTrue( $item->getId() !== null ); 81 | $this->assertEquals( $item->getId(), $itemSaved->getId() ); 82 | $this->assertEquals( $item->getSiteId(), $itemSaved->getSiteId() ); 83 | $this->assertEquals( $item->getParentId(), $itemSaved->getParentId() ); 84 | $this->assertEquals( $item->getSalutation(), $itemSaved->getSalutation() ); 85 | $this->assertEquals( $item->getType(), $itemSaved->getType() ); 86 | $this->assertEquals( $item->getCompany(), $itemSaved->getCompany() ); 87 | $this->assertEquals( $item->getVatID(), $itemSaved->getVatID() ); 88 | $this->assertEquals( $item->getTitle(), $itemSaved->getTitle() ); 89 | $this->assertEquals( $item->getFirstname(), $itemSaved->getFirstname() ); 90 | $this->assertEquals( $item->getLastname(), $itemSaved->getLastname() ); 91 | $this->assertEquals( $item->getAddress1(), $itemSaved->getAddress1() ); 92 | $this->assertEquals( $item->getAddress2(), $itemSaved->getAddress2() ); 93 | $this->assertEquals( $item->getAddress3(), $itemSaved->getAddress3() ); 94 | $this->assertEquals( $item->getPostal(), $itemSaved->getPostal() ); 95 | $this->assertEquals( $item->getCity(), $itemSaved->getCity() ); 96 | $this->assertEquals( $item->getState(), $itemSaved->getState() ); 97 | $this->assertEquals( $item->getLanguageId(), $itemSaved->getLanguageId() ); 98 | $this->assertEquals( $item->getCountryId(), $itemSaved->getCountryId() ); 99 | $this->assertEquals( $item->getTelephone(), $itemSaved->getTelephone() ); 100 | $this->assertEquals( $item->getMobile(), $itemSaved->getMobile() ); 101 | $this->assertEquals( $item->getEMail(), $itemSaved->getEMail() ); 102 | $this->assertEquals( $item->getTelefax(), $itemSaved->getTelefax() ); 103 | $this->assertEquals( $item->getWebsite(), $itemSaved->getWebsite() ); 104 | $this->assertEquals( $item->getLongitude(), $itemSaved->getLongitude() ); 105 | $this->assertEquals( $item->getLatitude(), $itemSaved->getLatitude() ); 106 | $this->assertEquals( $item->getBirthday(), $itemSaved->getBirthday() ); 107 | $this->assertEquals( $item->getPosition(), $itemSaved->getPosition() ); 108 | $this->assertEquals( $item->editor(), $itemSaved->editor() ); 109 | 110 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeCreated() ); 111 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeModified() ); 112 | 113 | $this->assertEquals( $itemExp->getId(), $itemUpd->getId() ); 114 | $this->assertEquals( $itemExp->getSiteId(), $itemUpd->getSiteId() ); 115 | $this->assertEquals( $itemExp->getParentId(), $itemUpd->getParentId() ); 116 | $this->assertEquals( $itemExp->getType(), $itemUpd->getType() ); 117 | $this->assertEquals( $itemExp->getSalutation(), $itemUpd->getSalutation() ); 118 | $this->assertEquals( $itemExp->getCompany(), $itemUpd->getCompany() ); 119 | $this->assertEquals( $itemExp->getVatID(), $itemUpd->getVatID() ); 120 | $this->assertEquals( $itemExp->getTitle(), $itemUpd->getTitle() ); 121 | $this->assertEquals( $itemExp->getFirstname(), $itemUpd->getFirstname() ); 122 | $this->assertEquals( $itemExp->getLastname(), $itemUpd->getLastname() ); 123 | $this->assertEquals( $itemExp->getAddress1(), $itemUpd->getAddress1() ); 124 | $this->assertEquals( $itemExp->getAddress2(), $itemUpd->getAddress2() ); 125 | $this->assertEquals( $itemExp->getAddress3(), $itemUpd->getAddress3() ); 126 | $this->assertEquals( $itemExp->getPostal(), $itemUpd->getPostal() ); 127 | $this->assertEquals( $itemExp->getCity(), $itemUpd->getCity() ); 128 | $this->assertEquals( $itemExp->getState(), $itemUpd->getState() ); 129 | $this->assertEquals( $itemExp->getLanguageId(), $itemUpd->getLanguageId() ); 130 | $this->assertEquals( $itemExp->getCountryId(), $itemUpd->getCountryId() ); 131 | $this->assertEquals( $itemExp->getTelephone(), $itemUpd->getTelephone() ); 132 | $this->assertEquals( $itemExp->getMobile(), $itemUpd->getMobile() ); 133 | $this->assertEquals( $itemExp->getEMail(), $itemUpd->getEMail() ); 134 | $this->assertEquals( $itemExp->getTelefax(), $itemUpd->getTelefax() ); 135 | $this->assertEquals( $itemExp->getWebsite(), $itemUpd->getWebsite() ); 136 | $this->assertEquals( $itemExp->getLongitude(), $itemUpd->getLongitude() ); 137 | $this->assertEquals( $itemExp->getLatitude(), $itemUpd->getLatitude() ); 138 | $this->assertEquals( $itemExp->getBirthday(), $itemUpd->getBirthday() ); 139 | $this->assertEquals( $itemExp->getPosition(), $itemUpd->getPosition() ); 140 | $this->assertEquals( $itemExp->editor(), $itemUpd->editor() ); 141 | 142 | $this->assertEquals( $itemExp->getTimeCreated(), $itemUpd->getTimeCreated() ); 143 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemUpd->getTimeModified() ); 144 | 145 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultSaved ); 146 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $resultUpd ); 147 | 148 | $this->expectException( '\\Aimeos\\MShop\\Exception' ); 149 | $this->object->get( $item->getId() ); 150 | } 151 | 152 | 153 | public function testCreateSearch() 154 | { 155 | $this->assertInstanceOf( '\\Aimeos\\Base\\Criteria\\Iface', $this->object->filter() ); 156 | } 157 | 158 | 159 | public function testSearchItem() 160 | { 161 | $search = $this->object->filter(); 162 | 163 | $conditions = array( 164 | $search->compare( '!=', 'customer.address.id', null ), 165 | $search->compare( '!=', 'customer.address.parentid', null ), 166 | $search->compare( '==', 'customer.address.type', 'delivery' ), 167 | $search->compare( '==', 'customer.address.company', 'Example company' ), 168 | $search->compare( '==', 'customer.address.vatid', 'DE999999999' ), 169 | $search->compare( '==', 'customer.address.salutation', 'mr' ), 170 | $search->compare( '==', 'customer.address.title', 'Dr' ), 171 | $search->compare( '==', 'customer.address.firstname', 'Our' ), 172 | $search->compare( '==', 'customer.address.lastname', 'Unittest' ), 173 | $search->compare( '==', 'customer.address.address1', 'Pickhuben' ), 174 | $search->compare( '==', 'customer.address.address2', '2-4' ), 175 | $search->compare( '==', 'customer.address.address3', '' ), 176 | $search->compare( '==', 'customer.address.postal', '20457' ), 177 | $search->compare( '==', 'customer.address.city', 'Hamburg' ), 178 | $search->compare( '==', 'customer.address.state', 'Hamburg' ), 179 | $search->compare( '==', 'customer.address.countryid', 'DE' ), 180 | $search->compare( '==', 'customer.address.languageid', 'de' ), 181 | $search->compare( '==', 'customer.address.telephone', '055544332211' ), 182 | $search->compare( '==', 'customer.address.telefax', '055544332212' ), 183 | $search->compare( '==', 'customer.address.mobile', '055544332213' ), 184 | $search->compare( '==', 'customer.address.email', 'test@example.com' ), 185 | $search->compare( '==', 'customer.address.website', 'www.example.com' ), 186 | $search->compare( '==', 'customer.address.longitude', '10.0' ), 187 | $search->compare( '==', 'customer.address.latitude', '50.0' ), 188 | $search->compare( '==', 'customer.address.birthday', '2000-01-01' ), 189 | $search->compare( '>=', 'customer.address.mtime', '1970-01-01 00:00:00' ), 190 | $search->compare( '>=', 'customer.address.ctime', '1970-01-01 00:00:00' ), 191 | $search->compare( '==', 'customer.address.editor', $this->editor ), 192 | ); 193 | $search->setConditions( $search->and( $conditions ) ); 194 | $this->assertEquals( 1, count( $this->object->search( $search ) ) ); 195 | } 196 | 197 | 198 | public function testSearchItemTotal() 199 | { 200 | $total = 0; 201 | $search = $this->object->filter(); 202 | 203 | $conditions = array( 204 | $search->compare( '~=', 'customer.address.company', 'Example company' ), 205 | $search->compare( '==', 'customer.address.editor', $this->editor ) 206 | ); 207 | 208 | $search->setConditions( $search->and( $conditions ) ); 209 | $search->slice( 0, 2 ); 210 | 211 | $results = $this->object->search( $search, [], $total ); 212 | 213 | $this->assertEquals( 2, count( $results ) ); 214 | $this->assertEquals( 3, $total ); 215 | 216 | foreach( $results as $id => $item ) { 217 | $this->assertEquals( $id, $item->getId() ); 218 | } 219 | } 220 | 221 | 222 | public function testGetSubManager() 223 | { 224 | $this->expectException( \LogicException::class ); 225 | $this->object->getSubManager( 'unknown' ); 226 | } 227 | 228 | 229 | public function testGetSubManagerInvalidName() 230 | { 231 | $this->expectException( \LogicException::class ); 232 | $this->object->getSubManager( 'address', 'unknown' ); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/Base/Mail/Message/Typo3.php: -------------------------------------------------------------------------------- 1 | charset = $charset; 36 | $this->object = $object; 37 | } 38 | 39 | 40 | /** 41 | * Adds a source e-mail address of the message. 42 | * 43 | * @param string $email Source e-mail address 44 | * @param string|null $name Name of the user sending the e-mail or null for no name 45 | * @return \Aimeos\Base\Mail\Message\Iface Message object 46 | */ 47 | public function from( string $email, ?string $name = null ) : Iface 48 | { 49 | if( $email ) 50 | { 51 | $class = '\Symfony\Component\Mime\Email'; 52 | 53 | if( class_exists( $class ) && $this->object instanceof $class ) { 54 | $this->object->addFrom( new \Symfony\Component\Mime\Address( $email, (string) $name ) ); 55 | } else { 56 | $this->object->addFrom( $email, $name ); 57 | } 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | 64 | /** 65 | * Adds a destination e-mail address of the target user mailbox. 66 | * 67 | * @param string $email Destination address of the target mailbox 68 | * @param string|null $name Name of the user owning the target mailbox or null for no name 69 | * @return \Aimeos\Base\Mail\Message\Iface Message object 70 | */ 71 | public function to( string $email, ?string $name = null ) : Iface 72 | { 73 | if( $email ) 74 | { 75 | $class = '\Symfony\Component\Mime\Email'; 76 | 77 | if( class_exists( $class ) && $this->object instanceof $class ) { 78 | $this->object->addTo( new \Symfony\Component\Mime\Address( $email, (string) $name ) ); 79 | } else { 80 | $this->object->addTo( $email, $name ); 81 | } 82 | } 83 | 84 | return $this; 85 | } 86 | 87 | 88 | /** 89 | * Adds a destination e-mail address for a copy of the message. 90 | * 91 | * @param string $email Destination address for a copy 92 | * @param string|null $name Name of the user owning the target mailbox or null for no name 93 | * @return \Aimeos\Base\Mail\Message\Iface Message object 94 | */ 95 | public function cc( string $email, ?string $name = null ) : Iface 96 | { 97 | if( $email ) 98 | { 99 | $class = '\Symfony\Component\Mime\Email'; 100 | 101 | if( class_exists( $class ) && $this->object instanceof $class ) { 102 | $this->object->addCc( new \Symfony\Component\Mime\Address( $email, (string) $name ) ); 103 | } else { 104 | $this->object->addCc( $email, $name ); 105 | } 106 | } 107 | 108 | return $this; 109 | } 110 | 111 | 112 | /** 113 | * Adds a destination e-mail address for a hidden copy of the message. 114 | * 115 | * @param array|string $email Destination address for a hidden copy 116 | * @return \Aimeos\Base\Mail\Message\Iface Message object 117 | */ 118 | public function bcc( $email ) : Iface 119 | { 120 | if( !empty( $email ) ) 121 | { 122 | $class = '\Symfony\Component\Mime\Email'; 123 | 124 | foreach( (array) $email as $addr ) 125 | { 126 | if( class_exists( $class ) && $this->object instanceof $class ) { 127 | $this->object->addBcc( new \Symfony\Component\Mime\Address( $addr ) ); 128 | } else { 129 | $this->object->addBcc( $addr ); 130 | } 131 | } 132 | } 133 | 134 | return $this; 135 | } 136 | 137 | 138 | /** 139 | * Adds the return e-mail address for the message. 140 | * 141 | * @param string $email E-mail address which should receive all replies 142 | * @param string|null $name Name of the user which should receive all replies or null for no name 143 | * @return \Aimeos\Base\Mail\Message\Iface Message object 144 | */ 145 | public function replyTo( string $email, ?string $name = null ) : Iface 146 | { 147 | if( $email ) 148 | { 149 | $class = '\Symfony\Component\Mime\Email'; 150 | 151 | if( class_exists( $class ) && $this->object instanceof $class ) { 152 | $this->object->addReplyTo( new \Symfony\Component\Mime\Address( $email, (string) $name ) ); 153 | } else { 154 | $this->object->addReplyTo( $email, $name ); 155 | } 156 | } 157 | 158 | return $this; 159 | } 160 | 161 | 162 | /** 163 | * Adds a custom header to the message. 164 | * 165 | * @param string $name Name of the custom e-mail header 166 | * @param string $value Text content of the custom e-mail header 167 | * @return \Aimeos\Base\Mail\Message\Iface Message object 168 | */ 169 | public function header( string $name, string $value ) : Iface 170 | { 171 | if( $name ) 172 | { 173 | $class = '\Symfony\Component\Mime\Email'; 174 | 175 | if( class_exists( $class ) && $this->object instanceof $class ) { 176 | $this->object->getHeaders()->add( new \Symfony\Component\Mime\Header\UnstructuredHeader( $name, $value ) ); 177 | } else { 178 | $this->object->getHeaders()->addTextHeader( $name, $value ); 179 | } 180 | } 181 | 182 | return $this; 183 | } 184 | 185 | 186 | /** 187 | * Sends the e-mail message to the mail server. 188 | * 189 | * @return \Aimeos\Base\Mail\Message\Iface Message object 190 | */ 191 | public function send() : Iface 192 | { 193 | $this->object->send(); 194 | return $this; 195 | } 196 | 197 | 198 | /** 199 | * Sets the e-mail address and name of the sender of the message (higher precedence than "From"). 200 | * 201 | * @param string $email Source e-mail address 202 | * @param string|null $name Name of the user who sent the message or null for no name 203 | * @return \Aimeos\Base\Mail\Message\Iface Message object 204 | */ 205 | public function sender( string $email, ?string $name = null ) : Iface 206 | { 207 | if( $email ) 208 | { 209 | $class = '\Symfony\Component\Mime\Email'; 210 | 211 | if( class_exists( $class ) && $this->object instanceof $class ) { 212 | $this->object->setSender( new \Symfony\Component\Mime\Address( $email, (string) $name ) ); 213 | } else { 214 | $this->object->setSender( $email, $name ); 215 | } 216 | } 217 | 218 | return $this; 219 | } 220 | 221 | 222 | /** 223 | * Sets the subject of the message. 224 | * 225 | * @param string $subject Subject of the message 226 | * @return \Aimeos\Base\Mail\Message\Iface Message object 227 | */ 228 | public function subject( string $subject ) : Iface 229 | { 230 | if( $subject ) 231 | { 232 | $class = '\Symfony\Component\Mime\Email'; 233 | 234 | if( class_exists( $class ) && $this->object instanceof $class ) { 235 | $this->object->setSubject( $subject ); 236 | } else { 237 | $this->object->setSubject( $subject ); 238 | } 239 | } 240 | 241 | return $this; 242 | } 243 | 244 | 245 | /** 246 | * Sets the text body of the message. 247 | * 248 | * @param string $message Text body of the message 249 | * @return \Aimeos\Base\Mail\Message\Iface Message object 250 | */ 251 | public function text( string $message ) : Iface 252 | { 253 | if( $message ) 254 | { 255 | $class = '\Symfony\Component\Mime\Email'; 256 | 257 | if( class_exists( $class ) && $this->object instanceof $class ) { 258 | $this->object->text( $message, $this->charset ); 259 | } elseif( class_exists( '\Swift_Mailer' ) ) { 260 | $this->object->setBody( $message ); 261 | } 262 | } 263 | 264 | return $this; 265 | } 266 | 267 | 268 | /** 269 | * Sets the HTML body of the message. 270 | * 271 | * @param string $message HTML body of the message 272 | * @return \Aimeos\Base\Mail\Message\Iface Message object 273 | */ 274 | public function html( string $message ) : Iface 275 | { 276 | if( $message ) 277 | { 278 | $class = '\Symfony\Component\Mime\Email'; 279 | 280 | if( class_exists( $class ) && $this->object instanceof $class ) { 281 | $this->object->html( $message, $this->charset ); 282 | } elseif( class_exists( '\Swift_Mailer' ) ) { 283 | $this->object->addPart( $message, 'text/html' ); 284 | } 285 | } 286 | 287 | return $this; 288 | } 289 | 290 | 291 | /** 292 | * Adds an attachment to the message. 293 | * 294 | * @param string|null $data Binary or string @author nose 295 | * @param string|null $filename Name of the attached file (or null if inline disposition is used) 296 | * @param string|null $mimetype Mime type of the attachment (e.g. "text/plain", "application/octet-stream", etc.) 297 | * @param string $disposition Type of the disposition ("attachment" or "inline") 298 | * @return \Aimeos\Base\Mail\Message\Iface Message object 299 | */ 300 | public function attach( ?string $data, ?string $filename = null, ?string $mimetype = null, string $disposition = 'attachment' ) : Iface 301 | { 302 | if( $data ) 303 | { 304 | $class = '\Symfony\Component\Mime\Email'; 305 | $mimetype = $mimetype ?: (new \finfo( FILEINFO_MIME_TYPE ))->buffer( $data ); 306 | $filename = $filename ?: md5( $data ); 307 | 308 | if( class_exists( $class ) && $this->object instanceof $class ) 309 | { 310 | $this->object->attach( $data, $filename, $mimetype ); 311 | } 312 | elseif( class_exists( '\Swift_Attachment' ) ) 313 | { 314 | $part = \Swift_Attachment::newInstance( $data, $filename, $mimetype ); 315 | $part->setDisposition( $disposition ); 316 | $this->object->attach( $part ); 317 | } 318 | else 319 | { 320 | throw new \RuntimeException( 'Symfony mailer or Swiftmailer package missing' ); 321 | } 322 | } 323 | 324 | return $this; 325 | } 326 | 327 | 328 | /** 329 | * Embeds an attachment into the message and returns its reference. 330 | * 331 | * @param string|null $data Binary or string 332 | * @param string|null $filename Name of the attached file 333 | * @param string|null $mimetype Mime type of the attachment (e.g. "text/plain", "application/octet-stream", etc.) 334 | * @return string Content ID for referencing the attachment in the HTML body 335 | */ 336 | public function embed( ?string $data, ?string $filename = null, ?string $mimetype = null ) : string 337 | { 338 | if( $data ) 339 | { 340 | $class = '\Symfony\Component\Mime\Email'; 341 | $mimetype = $mimetype ?: (new \finfo( FILEINFO_MIME_TYPE ))->buffer( $data ); 342 | $filename = $filename ?: md5( $data ); 343 | 344 | if( class_exists( $class ) && $this->object instanceof $class ) 345 | { 346 | $this->object->embed( $data, $filename, $mimetype ); 347 | return 'cid:' . $filename; 348 | } 349 | elseif( class_exists( '\Swift_EmbeddedFile' ) ) 350 | { 351 | $part = \Swift_EmbeddedFile::newInstance( $data, $filename, $mimetype ); 352 | return $this->object->embed( $part ); 353 | } 354 | else 355 | { 356 | throw new \RuntimeException( 'Symfony mailer or Swiftmailer package missing' ); 357 | } 358 | } 359 | 360 | return ''; 361 | } 362 | 363 | 364 | /** 365 | * Returns the internal TYPO3 mail message object. 366 | * 367 | * @return \TYPO3\CMS\Core\Mail\MailMessage TYPO3 mail message object 368 | */ 369 | public function object() : \TYPO3\CMS\Core\Mail\MailMessage 370 | { 371 | return $this->object; 372 | } 373 | 374 | 375 | /** 376 | * Clones the internal objects. 377 | */ 378 | public function __clone() 379 | { 380 | $this->object = clone $this->object; 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /tests/MShop/Customer/Manager/Typo3Test.php: -------------------------------------------------------------------------------- 1 | context = \TestHelper::context(); 22 | $this->context->config()->set( 'mshop/customer/manager/typo3/pid-default', 999999 ); 23 | 24 | $this->object = new \Aimeos\MShop\Customer\Manager\Typo3( $this->context ); 25 | $this->object = new \Aimeos\MShop\Common\Manager\Decorator\Lists( $this->object, $this->context ); 26 | $this->object = new \Aimeos\MShop\Common\Manager\Decorator\Property( $this->object, $this->context ); 27 | $this->object = new \Aimeos\MShop\Common\Manager\Decorator\Address( $this->object, $this->context ); 28 | $this->object->setObject( $this->object ); 29 | } 30 | 31 | 32 | protected function tearDown() : void 33 | { 34 | unset( $this->object, $this->item, $this->context ); 35 | } 36 | 37 | 38 | public function testAggregate() 39 | { 40 | $search = $this->object->filter(); 41 | $result = $this->object->aggregate( $search, 'customer.salutation' ); 42 | 43 | $this->assertEquals( 2, count( $result ) ); 44 | $this->assertArrayHasKey( 'mr', $result ); 45 | $this->assertEquals( 1, $result->get( 'mr' ) ); 46 | } 47 | 48 | 49 | public function testAggregateMultiple() 50 | { 51 | $cols = ['customer.salutation', 'customer.title']; 52 | $search = $this->object->filter()->order( $cols ); 53 | $result = $this->object->aggregate( $search, $cols ); 54 | 55 | $this->assertEquals( ['mr' => ['Dr' => 1], '' => ['' => 2]], $result->toArray() ); 56 | } 57 | 58 | 59 | public function testGetSearchAttributes() 60 | { 61 | foreach( $this->object->getSearchAttributes() as $attribute ) 62 | { 63 | $this->assertInstanceOf( '\\Aimeos\\Base\\Criteria\\Attribute\\Iface', $attribute ); 64 | } 65 | } 66 | 67 | 68 | public function testCreateItem() 69 | { 70 | $item = $this->object->create(); 71 | $this->assertInstanceOf( '\\Aimeos\\MShop\\Customer\\Item\\Iface', $item ); 72 | } 73 | 74 | 75 | public function testGetItem() 76 | { 77 | $domains = ['text', 'customer/property' => ['newsletter']]; 78 | $expected = $this->object->find( 'test@example.com', $domains ); 79 | $actual = $this->object->get( $expected->getId(), $domains ); 80 | 81 | $this->assertEquals( $expected, $actual ); 82 | $this->assertEquals( 1, count( $actual->getListItems( 'text' ) ) ); 83 | $this->assertEquals( 1, count( $actual->getRefItems( 'text' ) ) ); 84 | $this->assertEquals( 1, count( $actual->getPropertyItems() ) ); 85 | } 86 | 87 | 88 | public function testSaveUpdateDeleteItem() 89 | { 90 | $search = $this->object->filter(); 91 | $search->setConditions( $search->compare( '==', 'customer.code', 'test@example.com' ) ); 92 | 93 | if( ( $item = $this->object->search( $search )->first() ) === null ) { 94 | throw new \RuntimeException( 'No customer found.' ); 95 | } 96 | 97 | $item->setId( null ); 98 | $item->setCode( 'unitTest' ); 99 | $item->setLabel( 'unitTest' ); 100 | $item->setGroups( array( 1, 2, 3 ) ); 101 | $item = $this->object->save( $item ); 102 | $itemSaved = $this->object->get( $item->getId() ); 103 | 104 | $itemExp = clone $itemSaved; 105 | $itemExp->setCode( 'unitTest2' ); 106 | $itemExp->setLabel( 'unitTest2' ); 107 | $itemExp->setGroups( array( 2, 4 ) ); 108 | $itemExp = $this->object->save( $itemExp ); 109 | $itemUpd = $this->object->get( $itemExp->getId() ); 110 | 111 | $this->object->delete( $item->getId() ); 112 | 113 | 114 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $item ); 115 | $this->assertInstanceOf( \Aimeos\MShop\Common\Item\Iface::class, $itemExp ); 116 | 117 | $this->assertTrue( $item->getId() !== null ); 118 | $this->assertEquals( $item->getId(), $itemSaved->getId() ); 119 | $this->assertEquals( $item->getSiteId(), $itemSaved->getSiteId() ); 120 | $this->assertEquals( $item->getStatus(), $itemSaved->getStatus() ); 121 | $this->assertEquals( $item->getCode(), $itemSaved->getCode() ); 122 | $this->assertEquals( $item->getLabel(), $itemSaved->getLabel() ); 123 | $this->assertEquals( $item->getPassword(), $itemSaved->getPassword() ); 124 | $this->assertEquals( $item->getGroups(), $itemSaved->getGroups() ); 125 | 126 | $this->assertEquals( 'ai-typo3', $itemSaved->editor() ); 127 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeCreated() ); 128 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemSaved->getTimeModified() ); 129 | 130 | $this->assertEquals( $itemExp->getId(), $itemUpd->getId() ); 131 | $this->assertEquals( $itemExp->getSiteId(), $itemUpd->getSiteId() ); 132 | $this->assertEquals( $itemExp->getStatus(), $itemUpd->getStatus() ); 133 | $this->assertEquals( $itemExp->getCode(), $itemUpd->getCode() ); 134 | $this->assertEquals( $itemExp->getLabel(), $itemUpd->getLabel() ); 135 | $this->assertEquals( $itemExp->getPassword(), $itemUpd->getPassword() ); 136 | $this->assertEquals( $itemExp->getGroups(), $itemUpd->getGroups() ); 137 | 138 | $this->assertEquals( 'ai-typo3', $itemUpd->editor() ); 139 | $this->assertEquals( $itemExp->getTimeCreated(), $itemUpd->getTimeCreated() ); 140 | $this->assertMatchesRegularExpression( '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $itemUpd->getTimeModified() ); 141 | 142 | 143 | $this->expectException( '\\Aimeos\\MShop\\Exception' ); 144 | $this->object->get( $item->getId() ); 145 | } 146 | 147 | 148 | public function testGetSaveAddressItems() 149 | { 150 | $item = $this->object->find( 'test@example.com', ['customer/address'] ); 151 | 152 | $item->setId( null )->setCode( 'xyz' ); 153 | $item->getPaymentAddress()->setEmail( 'unittest@xyz.com' ); 154 | $item->addAddressItem( new \Aimeos\MShop\Customer\Item\Address\Standard( 'customer.address.' ) ); 155 | $this->object->save( $item ); 156 | 157 | $item2 = $this->object->find( 'xyz', ['customer/address'] ); 158 | 159 | $this->object->delete( $item->getId() ); 160 | 161 | $this->assertEquals( 2, count( $item->getAddressItems() ) ); 162 | $this->assertEquals( 2, count( $item2->getAddressItems() ) ); 163 | } 164 | 165 | 166 | public function testGetSavePropertyItems() 167 | { 168 | $item = $this->object->find( 'test@example.com', ['customer/property'] ); 169 | 170 | $item->setId( null )->setCode( 'xyz' ); 171 | $item->getPaymentAddress()->setEmail( 'unittest@xyz.com' ); 172 | $this->object->save( $item ); 173 | 174 | $item2 = $this->object->find( 'xyz', ['customer/property'] ); 175 | 176 | $this->object->delete( $item->getId() ); 177 | 178 | $this->assertEquals( 1, count( $item->getPropertyItems() ) ); 179 | $this->assertEquals( 1, count( $item2->getPropertyItems() ) ); 180 | } 181 | 182 | 183 | public function testCreateSearch() 184 | { 185 | $this->assertInstanceOf( '\\Aimeos\\Base\\Criteria\\Iface', $this->object->filter() ); 186 | } 187 | 188 | 189 | public function testSearchItems() 190 | { 191 | $item = $this->object->find( 'test@example.com', ['text'] ); 192 | $listItem = $item->getListItems( 'text', 'default' )->first( new \RuntimeException( 'No list item found' ) ); 193 | 194 | $search = $this->object->filter(); 195 | 196 | $expr = []; 197 | $expr[] = $search->compare( '!=', 'customer.id', null ); 198 | $expr[] = $search->compare( '==', 'customer.label', 'unitCustomer001' ); 199 | $expr[] = $search->compare( '==', 'customer.code', 'test@example.com' ); 200 | $expr[] = $search->compare( '>=', 'customer.password', '' ); 201 | $expr[] = $search->compare( '==', 'customer.status', 1 ); 202 | $expr[] = $search->compare( '>', 'customer.mtime', '1970-01-01 00:00:00' ); 203 | $expr[] = $search->compare( '>', 'customer.ctime', '1970-01-01 00:00:00' ); 204 | $expr[] = $search->compare( '==', 'customer.editor', $this->context->editor() ); 205 | 206 | $expr[] = $search->compare( '==', 'customer.salutation', 'mr' ); 207 | $expr[] = $search->compare( '==', 'customer.company', 'Example company' ); 208 | $expr[] = $search->compare( '==', 'customer.vatid', 'DE999999999' ); 209 | $expr[] = $search->compare( '==', 'customer.title', 'Dr' ); 210 | $expr[] = $search->compare( '==', 'customer.firstname', 'Our' ); 211 | $expr[] = $search->compare( '==', 'customer.lastname', 'Unittest' ); 212 | $expr[] = $search->compare( '=~', 'customer.address1', 'Pickhuben' ); 213 | $expr[] = $search->compare( '==', 'customer.postal', '20457' ); 214 | $expr[] = $search->compare( '==', 'customer.city', 'Hamburg' ); 215 | $expr[] = $search->compare( '==', 'customer.state', 'Hamburg' ); 216 | $expr[] = $search->compare( '==', 'customer.languageid', 'de' ); 217 | $expr[] = $search->compare( '==', 'customer.countryid', 'DE' ); 218 | $expr[] = $search->compare( '==', 'customer.telephone', '055544332211' ); 219 | $expr[] = $search->compare( '==', 'customer.email', 'test@example.com' ); 220 | $expr[] = $search->compare( '==', 'customer.telefax', '055544332212' ); 221 | $expr[] = $search->compare( '==', 'customer.website', 'www.example.com' ); 222 | $expr[] = $search->compare( '==', 'customer.longitude', '10.0' ); 223 | $expr[] = $search->compare( '==', 'customer.latitude', '50.0' ); 224 | $expr[] = $search->compare( '==', 'customer.birthday', '1999-01-01' ); 225 | 226 | $param = ['text', 'default', $listItem->getRefId()]; 227 | $expr[] = $search->compare( '!=', $search->make( 'customer:has', $param ), null ); 228 | 229 | $param = ['text', 'default']; 230 | $expr[] = $search->compare( '!=', $search->make( 'customer:has', $param ), null ); 231 | 232 | $param = ['text']; 233 | $expr[] = $search->compare( '!=', $search->make( 'customer:has', $param ), null ); 234 | 235 | $param = ['newsletter', null, '1']; 236 | $expr[] = $search->compare( '!=', $search->make( 'customer:prop', $param ), null ); 237 | 238 | $param = ['newsletter', null]; 239 | $expr[] = $search->compare( '!=', $search->make( 'customer:prop', $param ), null ); 240 | 241 | $param = ['newsletter']; 242 | $expr[] = $search->compare( '!=', $search->make( 'customer:prop', $param ), null ); 243 | 244 | $expr[] = $search->compare( '!=', 'customer.address.id', null ); 245 | $expr[] = $search->compare( '!=', 'customer.address.parentid', null ); 246 | $expr[] = $search->compare( '==', 'customer.address.type', 'delivery' ); 247 | $expr[] = $search->compare( '==', 'customer.address.salutation', 'mr' ); 248 | $expr[] = $search->compare( '==', 'customer.address.company', 'Example company' ); 249 | $expr[] = $search->compare( '==', 'customer.address.vatid', 'DE999999999' ); 250 | $expr[] = $search->compare( '==', 'customer.address.title', 'Dr' ); 251 | $expr[] = $search->compare( '==', 'customer.address.firstname', 'Our' ); 252 | $expr[] = $search->compare( '==', 'customer.address.lastname', 'Unittest' ); 253 | $expr[] = $search->compare( '==', 'customer.address.address1', 'Pickhuben' ); 254 | $expr[] = $search->compare( '==', 'customer.address.address2', '2-4' ); 255 | $expr[] = $search->compare( '==', 'customer.address.address3', '' ); 256 | $expr[] = $search->compare( '==', 'customer.address.postal', '20457' ); 257 | $expr[] = $search->compare( '==', 'customer.address.city', 'Hamburg' ); 258 | $expr[] = $search->compare( '==', 'customer.address.state', 'Hamburg' ); 259 | $expr[] = $search->compare( '==', 'customer.address.languageid', 'de' ); 260 | $expr[] = $search->compare( '==', 'customer.address.countryid', 'DE' ); 261 | $expr[] = $search->compare( '==', 'customer.address.telephone', '055544332211' ); 262 | $expr[] = $search->compare( '==', 'customer.address.email', 'test@example.com' ); 263 | $expr[] = $search->compare( '==', 'customer.address.telefax', '055544332212' ); 264 | $expr[] = $search->compare( '==', 'customer.address.website', 'www.example.com' ); 265 | $expr[] = $search->compare( '==', 'customer.address.longitude', '10.0' ); 266 | $expr[] = $search->compare( '==', 'customer.address.latitude', '50.0' ); 267 | $expr[] = $search->compare( '==', 'customer.address.position', 0 ); 268 | $expr[] = $search->compare( '>=', 'customer.address.mtime', '1970-01-01 00:00:00' ); 269 | $expr[] = $search->compare( '>=', 'customer.address.ctime', '1970-01-01 00:00:00' ); 270 | $expr[] = $search->compare( '==', 'customer.address.editor', $this->context->editor() ); 271 | $expr[] = $search->compare( '==', 'customer.address.birthday', '2000-01-01' ); 272 | 273 | $search->setConditions( $search->and( $expr ) ); 274 | $result = $this->object->search( $search ); 275 | 276 | $this->assertEquals( 1, count( $result ) ); 277 | $this->assertEquals( 1, count( $result->first()->getGroups() ) ); 278 | } 279 | 280 | 281 | public function testSearchItemsTotal() 282 | { 283 | $search = $this->object->filter(); 284 | $search->slice( 0, 2 ); 285 | 286 | $total = 0; 287 | $results = $this->object->search( $search, [], $total ); 288 | 289 | $this->assertEquals( 2, count( $results ) ); 290 | $this->assertEquals( 3, $total ); 291 | } 292 | 293 | 294 | public function testSearchItemsCriteria() 295 | { 296 | $search = $this->object->filter( true ); 297 | $results = $this->object->search( $search ); 298 | 299 | $this->assertEquals( 2, count( $results ) ); 300 | 301 | foreach( $results as $itemId => $item ) { 302 | $this->assertEquals( $itemId, $item->getId() ); 303 | } 304 | } 305 | 306 | 307 | public function testSearchItemsRef() 308 | { 309 | $search = $this->object->filter(); 310 | $search->setConditions( $search->compare( '==', 'customer.code', 'test@example.com' ) ); 311 | 312 | if( ( $item = $this->object->search( $search, ['customer/address', 'text'] ) ) === null ) { 313 | throw new \Exception( 'No customer item for "test@example.com" available' ); 314 | } 315 | 316 | $this->assertEquals( 1, count( $item->getRefItems( 'text' ) ) ); 317 | $this->assertEquals( 1, count( $item->getAddressItems() ) ); 318 | } 319 | 320 | 321 | public function testGetSubManager() 322 | { 323 | $this->expectException( \LogicException::class ); 324 | $this->object->getSubManager( 'unknown' ); 325 | } 326 | 327 | 328 | public function testGetSubManagerInvalidName() 329 | { 330 | $this->expectException( \LogicException::class ); 331 | $this->object->getSubManager( 'address', 'unknown' ); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/MShop/Group/Manager/Typo3.php: -------------------------------------------------------------------------------- 1 | array( 25 | 'code' => 'group.id', 26 | 'internalcode' => 'mgro."uid"', 27 | 'label' => 'Group ID', 28 | 'type' => 'int', 29 | ), 30 | 'group.code' => array( 31 | 'code' => 'group.code', 32 | 'internalcode' => 'mgro."title"', 33 | 'label' => 'Group code', 34 | 'type' => 'string', 35 | ), 36 | 'group.label' => array( 37 | 'code' => 'group.label', 38 | 'internalcode' => 'mgro."description"', 39 | 'label' => 'Group label', 40 | 'type' => 'string', 41 | ), 42 | 'group.ctime'=> array( 43 | 'code' => 'group.ctime', 44 | 'internalcode' => 'mgro."crdate"', 45 | 'label' => 'Group creation time', 46 | 'type' => 'datetime', 47 | ), 48 | 'group.mtime'=> array( 49 | 'code' => 'group.mtime', 50 | 'internalcode' => 'mgro."tstamp"', 51 | 'label' => 'Group modification time', 52 | 'type' => 'datetime', 53 | ), 54 | 'group.editor'=> array( 55 | 'code' => 'group.editor', 56 | 'internalcode' => '', 57 | 'label' => 'Group editor', 58 | 'type' => 'string', 59 | ), 60 | ); 61 | 62 | private array $plugins = []; 63 | private array $reverse = []; 64 | private int $pid; 65 | 66 | 67 | /** 68 | * Initializes the group manager object 69 | * 70 | * @param \Aimeos\MShop\ContextIface $context Context object with required objects 71 | */ 72 | public function __construct( \Aimeos\MShop\ContextIface $context ) 73 | { 74 | parent::__construct( $context ); 75 | 76 | $plugin = new \Aimeos\Base\Criteria\Plugin\T3Datetime(); 77 | $this->plugins['ctime'] = $this->reverse['crdate'] = $plugin; 78 | $this->plugins['mtime'] = $this->reverse['tstamp'] = $plugin; 79 | 80 | /** mshop/group/manager/typo3/pid-default 81 | * Page ID the group records are assigned to 82 | * 83 | * In TYPO3, you can assign fe_group records to different sysfolders based 84 | * on their page ID. These sysfolders can be use for user authorization and 85 | * therefore, you need to assign the correct page ID to groups 86 | * created or modified by the Aimeos admin backend. 87 | * 88 | * @param int TYPO3 page ID 89 | * @since 2018.10 90 | * @see mshop/customer/manager/typo3/pid-default 91 | */ 92 | $this->pid = (int) $context->config()->get( 'mshop/customer/manager/typo3/pid-default', 0 ); 93 | $this->pid = (int) $context->config()->get( 'mshop/group/manager/typo3/pid-default', $this->pid ); 94 | } 95 | 96 | 97 | /** 98 | * Removes old entries from the database 99 | * 100 | * @param integer[] $siteids List of IDs for sites whose entries should be deleted 101 | * @return \Aimeos\MShop\Common\Manager\Iface Same object for fluent interface 102 | */ 103 | public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface 104 | { 105 | $path = 'mshop/group/manager/submanagers'; 106 | 107 | foreach( $this->context()->config()->get( $path, [] ) as $domain ) { 108 | $this->object()->getSubManager( $domain )->clear( $siteids ); 109 | } 110 | 111 | return $this; 112 | } 113 | 114 | 115 | /** 116 | * Removes multiple items. 117 | * 118 | * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items 119 | * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls 120 | */ 121 | public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface 122 | { 123 | /** mshop/group/manager/typo3/delete/mysql 124 | * Deletes the items matched by the given IDs from the database 125 | * 126 | * @see mshop/group/manager/typo3/delete/ansi 127 | */ 128 | 129 | /** mshop/group/manager/typo3/delete/ansi 130 | * Deletes the items matched by the given IDs from the database 131 | * 132 | * Removes the records specified by the given IDs from the group 133 | * database. The records must be from the site that is configured via the 134 | * context item. 135 | * 136 | * The ":cond" placeholder is replaced by the name of the ID column and 137 | * the given ID or list of IDs while the site ID is bound to the question 138 | * mark. 139 | * 140 | * The SQL statement should conform to the ANSI standard to be 141 | * compatible with most relational database systems. This also 142 | * includes using double quotes for table and column names. 143 | * 144 | * @param string SQL statement for deleting items 145 | * @since 2015.08 146 | * @category Developer 147 | * @see mshop/group/manager/typo3/insert/ansi 148 | * @see mshop/group/manager/typo3/update/ansi 149 | * @see mshop/group/manager/typo3/newid/ansi 150 | * @see mshop/group/manager/typo3/search/ansi 151 | * @see mshop/group/manager/typo3/count/ansi 152 | */ 153 | $path = 'mshop/group/manager/typo3/delete'; 154 | 155 | return $this->deleteItemsBase( $itemIds, $path, false, 'uid' ); 156 | } 157 | 158 | 159 | /** 160 | * Returns the additional column/search definitions 161 | * 162 | * @return array Associative list of column names as keys and items implementing \Aimeos\Base\Criteria\Attribute\Iface 163 | */ 164 | public function getSaveAttributes() : array 165 | { 166 | return []; 167 | } 168 | 169 | 170 | /** 171 | * Returns the attributes that can be used for searching 172 | * 173 | * @param bool $withsub Return attributes of sub-managers too if true 174 | * @return array List of attribute items implementing \Aimeos\Base\Criteria\Attribute\Iface 175 | */ 176 | public function getSearchAttributes( bool $withsub = true ) : array 177 | { 178 | $path = 'mshop/group/manager/submanagers'; 179 | 180 | return $this->getSearchAttributesBase( $this->searchConfig, $path, [], $withsub ); 181 | } 182 | 183 | 184 | /** 185 | * Returns a new manager for group extensions 186 | * 187 | * @param string $manager Name of the sub manager type in lower case 188 | * @param string|null $name Name of the implementation, will be from configuration (or Default) if null 189 | * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions 190 | */ 191 | public function getSubManager( string $manager, ?string $name = null ) : \Aimeos\MShop\Common\Manager\Iface 192 | { 193 | return $this->getSubManagerBase( 'group/group', $manager, ( $name === null ? 'Typo3' : $name ) ); 194 | } 195 | 196 | 197 | /** 198 | * Inserts a new or updates an existing group item 199 | * 200 | * @param \Aimeos\MShop\Group\Item\Iface $item Group item 201 | * @param boolean $fetch True if the new ID should be returned in the item 202 | * @return \Aimeos\MShop\Group\Item\Iface $item Updated item including the generated ID 203 | */ 204 | protected function saveItem( \Aimeos\MShop\Group\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Group\Item\Iface 205 | { 206 | if( !$item->isModified() ) { 207 | return $item; 208 | } 209 | 210 | $context = $this->context(); 211 | $conn = $context->db( $this->getResourceName() ); 212 | $time = date_create_from_format( 'Y-m-d H:i:s', $context->datetime() )->getTimestamp(); 213 | 214 | $id = $item->getId(); 215 | $columns = $this->object()->getSaveAttributes(); 216 | 217 | if( $id === null ) 218 | { 219 | /** mshop/group/manager/typo3/insert/mysql 220 | * Inserts a new group record into the database table 221 | * 222 | * @see mshop/group/manager/typo3/insert/ansi 223 | */ 224 | 225 | /** mshop/group/manager/typo3/insert/ansi 226 | * Inserts a new group record into the database table 227 | * 228 | * Items with no ID yet (i.e. the ID is NULL) will be created in 229 | * the database and the newly created ID retrieved afterwards 230 | * using the "newid" SQL statement. 231 | * 232 | * The SQL statement must be a string suitable for being used as 233 | * prepared statement. It must include question marks for binding 234 | * the values from the group item to the statement before 235 | * they are sent to the database server. The number of question 236 | * marks must be the same as the number of columns listed in the 237 | * INSERT statement. The order of the columns must correspond to 238 | * the order in the save() method, so the correct values are 239 | * bound to the columns. 240 | * 241 | * The SQL statement should conform to the ANSI standard to be 242 | * compatible with most relational database systems. This also 243 | * includes using double quotes for table and column names. 244 | * 245 | * @param string SQL statement for inserting records 246 | * @since 2015.08 247 | * @category Developer 248 | * @see mshop/group/manager/typo3/update/ansi 249 | * @see mshop/group/manager/typo3/newid/ansi 250 | * @see mshop/group/manager/typo3/delete/ansi 251 | * @see mshop/group/manager/typo3/search/ansi 252 | * @see mshop/group/manager/typo3/count/ansi 253 | */ 254 | $path = 'mshop/group/manager/typo3/insert'; 255 | $sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ) ); 256 | } 257 | else 258 | { 259 | /** mshop/group/manager/typo3/update/mysql 260 | * Updates an existing group record in the database 261 | * 262 | * @see mshop/group/manager/typo3/update/ansi 263 | */ 264 | 265 | /** mshop/group/manager/typo3/update/ansi 266 | * Updates an existing group record in the database 267 | * 268 | * Items which already have an ID (i.e. the ID is not NULL) will 269 | * be updated in the database. 270 | * 271 | * The SQL statement must be a string suitable for being used as 272 | * prepared statement. It must include question marks for binding 273 | * the values from the group item to the statement before 274 | * they are sent to the database server. The order of the columns 275 | * must correspond to the order in the save() method, so the 276 | * correct values are bound to the columns. 277 | * 278 | * The SQL statement should conform to the ANSI standard to be 279 | * compatible with most relational database systems. This also 280 | * includes using double quotes for table and column names. 281 | * 282 | * @param string SQL statement for updating records 283 | * @since 2015.08 284 | * @category Developer 285 | * @see mshop/group/manager/typo3/insert/ansi 286 | * @see mshop/group/manager/typo3/newid/ansi 287 | * @see mshop/group/manager/typo3/delete/ansi 288 | * @see mshop/group/manager/typo3/search/ansi 289 | * @see mshop/group/manager/typo3/count/ansi 290 | */ 291 | $path = 'mshop/group/manager/typo3/update'; 292 | $sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false ); 293 | } 294 | 295 | $idx = 1; 296 | $stmt = $this->getCachedStatement( $conn, $path, $sql ); 297 | 298 | foreach( $columns as $name => $entry ) { 299 | $stmt->bind( $idx++, $item->get( $name ), \Aimeos\Base\Criteria\SQL::type( $entry->getType() ) ); 300 | } 301 | 302 | $stmt->bind( $idx++, $this->pid, \Aimeos\Base\DB\Statement\Base::PARAM_INT ); 303 | $stmt->bind( $idx++, $item->getCode() ); 304 | $stmt->bind( $idx++, $item->getLabel() ); 305 | $stmt->bind( $idx++, $time, \Aimeos\Base\DB\Statement\Base::PARAM_INT ); // mtime 306 | 307 | if( $id !== null ) { 308 | $stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT ); 309 | $item->setId( $id ); 310 | } else { 311 | $stmt->bind( $idx++, $time, \Aimeos\Base\DB\Statement\Base::PARAM_INT ); // ctime 312 | } 313 | 314 | $stmt->execute()->finish(); 315 | 316 | if( $id === null && $fetch === true ) 317 | { 318 | /** mshop/group/manager/typo3/newid/mysql 319 | * Retrieves the ID generated by the database when inserting a new record 320 | * 321 | * @see mshop/group/manager/typo3/newid/ansi 322 | */ 323 | 324 | /** mshop/group/manager/typo3/newid/ansi 325 | * Retrieves the ID generated by the database when inserting a new record 326 | * 327 | * As soon as a new record is inserted into the database table, 328 | * the database server generates a new and unique identifier for 329 | * that record. This ID can be used for retrieving, updating and 330 | * deleting that specific record from the table again. 331 | * 332 | * For MySQL: 333 | * SELECT LAST_INSERT_ID() 334 | * For PostgreSQL: 335 | * SELECT currval('seq_mcus_id') 336 | * For SQL Server: 337 | * SELECT SCOPE_IDENTITY() 338 | * For Oracle: 339 | * SELECT "seq_mcus_id".CURRVAL FROM DUAL 340 | * 341 | * There's no way to retrive the new ID by a SQL statements that 342 | * fits for most database servers as they implement their own 343 | * specific way. 344 | * 345 | * @param string SQL statement for retrieving the last inserted record ID 346 | * @since 2015.08 347 | * @category Developer 348 | * @see mshop/group/manager/typo3/insert/ansi 349 | * @see mshop/group/manager/typo3/update/ansi 350 | * @see mshop/group/manager/typo3/delete/ansi 351 | * @see mshop/group/manager/typo3/search/ansi 352 | * @see mshop/group/manager/typo3/count/ansi 353 | */ 354 | $path = 'mshop/group/manager/typo3/newid'; 355 | $item->setId( $this->newId( $conn, $path ) ); 356 | } 357 | 358 | return $item; 359 | } 360 | 361 | 362 | /** 363 | * Returns the item objects matched by the given search criteria. 364 | * 365 | * @param \Aimeos\Base\Criteria\Iface $search Search criteria object 366 | * @param array $ref List of domain items that should be fetched too 367 | * @param int|null &$total Number of items that are available in total 368 | * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Group\Item\Iface 369 | * @throws \Aimeos\MShop\Exception If retrieving items failed 370 | */ 371 | public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], ?int &$total = null ) : \Aimeos\Map 372 | { 373 | $map = []; 374 | $context = $this->context(); 375 | $conn = $context->db( $this->getResourceName() ); 376 | 377 | $required = array( 'group' ); 378 | $level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL; 379 | 380 | /** mshop/group/manager/typo3/search 381 | * Retrieves the records matched by the given criteria in the database 382 | * 383 | * Fetches the records matched by the given criteria from the group 384 | * database. The records must be from one of the sites that are 385 | * configured via the context item. If the current site is part of 386 | * a tree of sites, the SELECT statement can retrieve all records 387 | * from the current site and the complete sub-tree of sites. 388 | * 389 | * As the records can normally be limited by criteria from sub-managers, 390 | * their tables must be joined in the SQL context. This is done by 391 | * using the "internaldeps" property from the definition of the ID 392 | * column of the sub-managers. These internal dependencies specify 393 | * the JOIN between the tables and the used columns for joining. The 394 | * ":joins" placeholder is then replaced by the JOIN strings from 395 | * the sub-managers. 396 | * 397 | * To limit the records matched, conditions can be added to the given 398 | * criteria object. It can contain comparisons like column names that 399 | * must match specific values which can be combined by AND, OR or NOT 400 | * operators. The resulting string of SQL conditions replaces the 401 | * ":cond" placeholder before the statement is sent to the database 402 | * server. 403 | * 404 | * If the records that are retrieved should be ordered by one or more 405 | * columns, the generated string of column / sort direction pairs 406 | * replaces the ":order" placeholder. In case no ordering is required, 407 | * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/" 408 | * markers is removed to speed up retrieving the records. Columns of 409 | * sub-managers can also be used for ordering the result set but then 410 | * no index can be used. 411 | * 412 | * The number of returned records can be limited and can start at any 413 | * number between the begining and the end of the result set. For that 414 | * the ":size" and ":start" placeholders are replaced by the 415 | * corresponding values from the criteria object. The default values 416 | * are 0 for the start and 100 for the size value. 417 | * 418 | * The SQL statement should conform to the ANSI standard to be 419 | * compatible with most relational database systems. This also 420 | * includes using double quotes for table and column names. 421 | * 422 | * @param string SQL statement for searching items 423 | * @since 2015.08 424 | * @category Developer 425 | * @see mshop/group/manager/typo3/count 426 | */ 427 | $cfgPathSearch = 'mshop/group/manager/typo3/search'; 428 | 429 | /** mshop/group/manager/typo3/count 430 | * Counts the number of records matched by the given criteria in the database 431 | * 432 | * Counts all records matched by the given criteria from the group 433 | * database. The records must be from one of the sites that are 434 | * configured via the context item. If the current site is part of 435 | * a tree of sites, the statement can count all records from the 436 | * current site and the complete sub-tree of sites. 437 | * 438 | * As the records can normally be limited by criteria from sub-managers, 439 | * their tables must be joined in the SQL context. This is done by 440 | * using the "internaldeps" property from the definition of the ID 441 | * column of the sub-managers. These internal dependencies specify 442 | * the JOIN between the tables and the used columns for joining. The 443 | * ":joins" placeholder is then replaced by the JOIN strings from 444 | * the sub-managers. 445 | * 446 | * To limit the records matched, conditions can be added to the given 447 | * criteria object. It can contain comparisons like column names that 448 | * must match specific values which can be combined by AND, OR or NOT 449 | * operators. The resulting string of SQL conditions replaces the 450 | * ":cond" placeholder before the statement is sent to the database 451 | * server. 452 | * 453 | * Both, the strings for ":joins" and for ":cond" are the same as for 454 | * the "search" SQL statement. 455 | * 456 | * Contrary to the "search" statement, it doesn't return any records 457 | * but instead the number of records that have been found. As counting 458 | * thousands of records can be a long running task, the maximum number 459 | * of counted records is limited for performance reasons. 460 | * 461 | * The SQL statement should conform to the ANSI standard to be 462 | * compatible with most relational database systems. This also 463 | * includes using double quotes for table and column names. 464 | * 465 | * @param string SQL statement for counting items 466 | * @since 2015.08 467 | * @category Developer 468 | * @see mshop/group/manager/typo3/search 469 | */ 470 | $cfgPathCount = 'mshop/group/manager/typo3/count'; 471 | 472 | $results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level ); 473 | 474 | while( $row = $results->fetch() ) { 475 | $map[(string) $row['group.id']] = $this->createItemBase( $row ); 476 | } 477 | 478 | return map( $map ); 479 | } 480 | 481 | 482 | /** 483 | * Creates a new group item. 484 | * 485 | * @param array $values List of attributes for group item 486 | * @return \Aimeos\MShop\Group\Item\Iface New group item 487 | */ 488 | protected function createItemBase( array $values = [] ) : \Aimeos\MShop\Group\Item\Iface 489 | { 490 | $values['group.siteid'] = $this->context()->locale()->getSiteId(); 491 | 492 | if( array_key_exists( 'group.mtime', $values ) ) { 493 | $values['group.mtime'] = $this->reverse['tstamp']->reverse( $values['group.mtime'] ); 494 | } 495 | 496 | if( array_key_exists( 'group.ctime', $values ) ) { 497 | $values['group.ctime'] = $this->reverse['crdate']->reverse( $values['group.ctime'] ); 498 | } 499 | 500 | return new \Aimeos\MShop\Group\Item\Standard( 'group.', $values ); 501 | } 502 | } 503 | --------------------------------------------------------------------------------