├── .env.example ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .htaccess ├── .htrouter.php ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codeception.yml ├── composer.json ├── composer.lock ├── config ├── config.php └── providers.php ├── db └── migrations │ └── 1.0.0 │ ├── companies.dat │ ├── companies.php │ ├── contact.php │ ├── product_types.dat │ ├── product_types.php │ ├── products.dat │ ├── products.php │ ├── users.dat │ └── users.php ├── docker-compose.yml ├── docker ├── 8.0 │ ├── .bashrc │ ├── Dockerfile │ └── extra.ini └── 8.1 │ ├── .bashrc │ ├── Dockerfile │ └── extra.ini ├── migrations.php ├── phpcs.xml ├── psalm.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php └── js │ └── utils.js ├── src ├── Constants │ └── Status.php ├── Controllers │ ├── AboutController.php │ ├── CompaniesController.php │ ├── ContactController.php │ ├── ControllerBase.php │ ├── ErrorsController.php │ ├── IndexController.php │ ├── InvoicesController.php │ ├── ProductsController.php │ ├── ProducttypesController.php │ ├── RegisterController.php │ └── SessionController.php ├── Forms │ ├── CompaniesForm.php │ ├── ContactForm.php │ ├── LoginForm.php │ ├── ProductTypesForm.php │ ├── ProductsForm.php │ ├── ProfileForm.php │ └── RegisterForm.php ├── Models │ ├── Companies.php │ ├── Contact.php │ ├── ProductTypes.php │ ├── Products.php │ └── Users.php ├── Plugins │ ├── NotFoundPlugin.php │ └── SecurityPlugin.php └── Providers │ ├── ConfigProvider.php │ ├── DatabaseProvider.php │ ├── DispatcherProvider.php │ ├── FlashProvider.php │ ├── SessionBagProvider.php │ ├── SessionProvider.php │ ├── UrlProvider.php │ ├── ViewProvider.php │ └── VoltProvider.php ├── tests ├── _bootstrap.php ├── _data │ └── .gitkeep ├── _output │ └── .gitignore ├── _support │ ├── AcceptanceTester.php │ ├── FunctionalTester.php │ ├── Helper │ │ ├── Acceptance.php │ │ ├── Functional.php │ │ └── Unit.php │ ├── UnitTester.php │ └── _generated │ │ └── .gitignore ├── acceptance.suite.yml ├── acceptance │ └── Controllers │ │ ├── AboutControllerCest.php │ │ ├── ErrorsControllerCest.php │ │ └── IndexControllerCest.php ├── functional.suite.yml ├── functional │ ├── Forms │ │ └── ProductTypesFormTest.php │ └── Models │ │ └── ContactTest.php ├── unit.suite.yml └── unit │ ├── Controllers │ ├── AboutControllerTest.php │ ├── CompaniesControllerTest.php │ ├── ContactControllerTest.php │ ├── ControllerBaseTest.php │ ├── ErrorsControllerTest.php │ ├── IndexControllerTest.php │ ├── InvoicesControllerTest.php │ ├── ProductTypesControllerTest.php │ ├── ProductsControllerTest.php │ ├── RegisterControllerTest.php │ └── SessionControllerTest.php │ ├── Forms │ ├── CompaniesFormTest.php │ ├── ContactFormTest.php │ ├── LoginFormTest.php │ ├── ProductTypesFormTest.php │ ├── ProductsFormTest.php │ └── RegisterFormTest.php │ ├── Models │ ├── CompaniesTest.php │ ├── ContactTest.php │ ├── ProductTypesTest.php │ ├── ProductsTest.php │ └── UsersTest.php │ ├── Plugins │ ├── NotFoundPluginTest.php │ └── SecurityPluginTest.php │ └── Providers │ ├── ConfigProviderTest.php │ ├── DatabaseProviderTest.php │ ├── DispatcherProviderTest.php │ ├── FlashProviderTest.php │ ├── SessionBagProviderTest.php │ ├── SessionProviderTest.php │ ├── UrlProviderTest.php │ ├── ViewProviderTest.php │ └── VoltProviderTest.php ├── themes └── invo │ ├── about │ └── index.volt │ ├── companies │ ├── edit.volt │ ├── index.volt │ ├── new.volt │ └── search.volt │ ├── contact │ └── index.volt │ ├── errors │ ├── show401.volt │ ├── show404.volt │ └── show500.volt │ ├── index.volt │ ├── index │ └── index.volt │ ├── invoices │ ├── index.volt │ └── profile.volt │ ├── layouts │ ├── about.volt │ ├── companies.volt │ ├── contact.volt │ ├── errors.volt │ ├── index.volt │ ├── invoices.volt │ ├── main.volt │ ├── patial_tabs.volt │ ├── products.volt │ ├── producttypes.volt │ ├── register.volt │ └── session.volt │ ├── products │ ├── edit.volt │ ├── index.volt │ ├── new.volt │ └── search.volt │ ├── producttypes │ ├── edit.volt │ ├── index.volt │ ├── new.volt │ └── search.volt │ ├── register │ └── index.volt │ └── session │ └── index.volt └── var ├── cache └── volt │ └── .gitignore └── logs └── .gitignore /.env.example: -------------------------------------------------------------------------------- 1 | DB_ADAPTER=Mysql 2 | # Use invo-mysql for dockerized environment or change it to where your db host is 3 | DB_HOST=localhost 4 | DB_USERNAME=phalcon 5 | DB_PASSWORD=secret 6 | DB_DBNAME=phalcon_invo 7 | DB_CHARSET=utf8 8 | 9 | VIEWS_DIR=themes/invo/ 10 | BASE_URI=/ 11 | 12 | APP_URL=http://127.0.0.1:8000 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | DB_HOST: '127.0.0.1' 7 | 8 | jobs: 9 | run: 10 | runs-on: ubuntu-latest 11 | services: 12 | mysql: 13 | image: mysql:5.7 14 | ports: 15 | - "3306:3306" 16 | env: 17 | MYSQL_ROOT_PASSWORD: secret 18 | MYSQL_USER: phalcon 19 | MYSQL_DATABASE: phalcon_invo 20 | MYSQL_PASSWORD: secret 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | php: ['8.0', '8.1' ] 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | 30 | - name: Copy .env file 31 | run: cp .env.example .env 32 | 33 | - name: Setup PHP 34 | uses: shivammathur/setup-php@v2 35 | with: 36 | php-version: ${{ matrix.php }} 37 | tools: pecl 38 | extensions: mbstring, intl, json, phalcon-5.1.4 39 | coverage: xdebug 40 | 41 | - name: Validate composer.json and composer.lock 42 | run: composer validate 43 | 44 | - name: Install Composer dependencies 45 | run: composer install --prefer-dist --no-progress --no-suggest 46 | 47 | # Disabling temporarily 48 | # - name: Run Psalm 49 | # if: always() 50 | # run: vendor/bin/psalm --show-info=false 51 | 52 | - name: Run tests 53 | if: always() 54 | run: | 55 | sudo php -S 127.0.0.1:8000 -t public/ & 56 | vendor/bin/codecept build 57 | vendor/bin/codecept run acceptance 58 | vendor/bin/codecept run unit --coverage-xml=unit-coverage.xml 59 | vendor/bin/codecept run functional --coverage-xml=functional-coverage.xml 60 | 61 | - name: Upload coverage to Codecov 62 | if: success() 63 | uses: codecov/codecov-action@v3 64 | with: 65 | token: ${{secrets.CODECOV_TOKEN}} 66 | directory: ./tests/_output/ 67 | files: unit-coverage.xml,functional-coverage.xml 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/config/config.ini.dev 2 | cache/volt/*.php 3 | .DS_Store* 4 | ehthumbs.db 5 | Icon? 6 | Thumbs.db 7 | .project 8 | .settings 9 | .buildpath 10 | *.komodoproject 11 | *.sublime-project 12 | *.sublime-workspace 13 | .idea/ 14 | vendor/ 15 | composer.phar 16 | /.env 17 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteRule ^$ public/ [L] 4 | RewriteRule (.*) public/$1 [L] 5 | 6 | -------------------------------------------------------------------------------- /.htrouter.php: -------------------------------------------------------------------------------- 1 | 12 | Phalcon Team 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-present, Phalcon Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # INVO Application 2 | 3 | [Phalcon][1] is a web framework delivered as a C extension providing high 4 | performance and lower resource consumption. 5 | 6 | This is a sample application for the Phalcon PHP Framework. We expect to 7 | implement as many features as possible to showcase the framework and its 8 | potential. 9 | 10 | Please write us if you have any feedback. 11 | 12 | Thanks. 13 | 14 | ## NOTE 15 | 16 | The master branch will always contain the latest stable version. If you wish 17 | to check older versions or newer ones currently under development, please 18 | switch to the relevant branch. 19 | 20 | ## Get Started 21 | 22 | ### Requirements 23 | 24 | * PHP >= 7.4 25 | * [Apache][2] Web Server with [mod_rewrite][3] enabled or [Nginx][4] Web Server 26 | * Latest stable [Phalcon Framework release][5] extension enabled 27 | * [MySQL][6] >= 5.7 28 | 29 | ### Installation 30 | 31 | 1. Copy project to local environment - `git clone git@github.com:phalcon/invo.git` 32 | 2. Copy file `cp .env.example .env` 33 | 3. Edit .env file with your DB connection information 34 | 4. Run DB migrations `vendor/bin/phalcon-migrations run --config=migrations.php` 35 | 36 | If you do not have PHP installed on your machine or do not wish to install it, you 37 | can run the application in a docker container. You will need [docker][9] and [docker-compose][10]. 38 | 39 | ```shell 40 | docker-compose up -d 41 | ``` 42 | 43 | will build and start your environment 44 | 45 | ```shell 46 | docker exec -it invo-8.0 /bin/bash 47 | ``` 48 | 49 | will allow you to enter the environment and run the tests. There is also `invo-8.1` 50 | as an option, if you wish to run an environment with PHP 8.1. 51 | 52 | To see the dockerized invo in action run: 53 | 54 | ```shell 55 | docker inspect invo-8.0 56 | ``` 57 | and make a note of the `IPAddress`. Type the address in your browser and you 58 | will see the invo application in action. 59 | 60 | ## Contributing 61 | 62 | See [CONTRIBUTING.md][7] 63 | 64 | ## Sponsors 65 | 66 | Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/phalcon#sponsor)] 67 | 68 | 69 | 70 | 71 | 72 | ## Backers 73 | 74 | Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/phalcon#backer)] 75 | 76 | 77 | 78 | 79 | 80 | ## License 81 | 82 | Invo is open-sourced software licensed under the [New BSD License][8]. © Phalcon Framework Team and contributors 83 | 84 | [1]: https://phalcon.io/ 85 | [2]: https://httpd.apache.org/ 86 | [3]: https://httpd.apache.org/docs/current/mod/mod_rewrite.html 87 | [4]: https://nginx.org/ 88 | [5]: https://github.com/phalcon/cphalcon/releases 89 | [6]: https://www.mysql.com/ 90 | [7]: https://github.com/phalcon/invo/blob/master/CONTRIBUTING.md 91 | [8]: https://github.com/phalcon/invo/blob/master/docs/LICENSE.md 92 | [9]: https://docs.docker.com/engine/install/ 93 | [10]: https://docs.docker.com/compose/install/ 94 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | tests: tests 3 | output: tests/_output 4 | data: tests/_data 5 | support: tests/_support 6 | envs: tests/_envs 7 | actor_suffix: Tester 8 | bootstrap: _bootstrap.php 9 | settings: 10 | colors: true 11 | extensions: 12 | enabled: 13 | - Codeception\Extension\RunFailed 14 | coverage: 15 | enabled: true 16 | include: 17 | - src/* 18 | params: 19 | # get params from environment vars 20 | - .env 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phalcon/invo", 3 | "type": "library", 4 | "description": "This is a sample application for the Phalcon PHP Framework", 5 | "keywords": [ 6 | "phalcon", 7 | "framework", 8 | "sample app", 9 | "invo" 10 | ], 11 | "homepage": "https://phalcon.io", 12 | "license": "BSD-3-Clause", 13 | "authors": [ 14 | { 15 | "name": "Contributors", 16 | "homepage": "https://github.com/phalcon/vokuro/graphs/contributors" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=8.0", 21 | "ext-phalcon": "^5.0.2", 22 | "vlucas/phpdotenv": "^5.4", 23 | "ext-pdo": "*" 24 | }, 25 | "require-dev": { 26 | "squizlabs/php_codesniffer": "^3.7", 27 | "vimeo/psalm": "^4.27", 28 | "codeception/codeception": "^5.0.0", 29 | "codeception/module-phpbrowser": "^3.0", 30 | "codeception/module-asserts": "^3.0", 31 | "phalcon/ide-stubs": "^5.0", 32 | "phalcon/migrations": "^3.0" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Invo\\": "src/" 37 | } 38 | }, 39 | "archive": { 40 | "exclude": [ 41 | "CONTRIBUTING.md" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'adapter' => $_ENV['DB_ADAPTER'] ?? 'Mysql', 9 | 'host' => $_ENV['DB_HOST'] ?? 'localhost', 10 | 'username' => $_ENV['DB_USERNAME'] ?? 'phalcon', 11 | 'password' => $_ENV['DB_PASSWORD'] ?? 'secret', 12 | 'dbname' => $_ENV['DB_DBNAME'] ?? 'phalcon_invo', 13 | 'charset' => $_ENV['DB_CHARSET'] ?? 'utf8', 14 | 'options' => [ 15 | PDO::ATTR_EMULATE_PREPARES => true 16 | ] 17 | ], 18 | 'application' => [ 19 | 'viewsDir' => $_ENV['VIEWS_DIR'] ?? 'themes/invo', 20 | 'baseUri' => $_ENV['BASE_URI'] ?? '/', 21 | ], 22 | ]); 23 | -------------------------------------------------------------------------------- /config/providers.php: -------------------------------------------------------------------------------- 1 | morphTable('companies', [ 20 | 'columns' => [ 21 | new Column( 22 | 'id', 23 | [ 24 | 'type' => Column::TYPE_INTEGER, 25 | 'unsigned' => true, 26 | 'notNull' => true, 27 | 'autoIncrement' => true, 28 | 'size' => 10, 29 | 'first' => true 30 | ] 31 | ), 32 | new Column( 33 | 'name', 34 | [ 35 | 'type' => Column::TYPE_VARCHAR, 36 | 'notNull' => true, 37 | 'size' => 70, 38 | 'after' => 'id' 39 | ] 40 | ), 41 | new Column( 42 | 'telephone', 43 | [ 44 | 'type' => Column::TYPE_VARCHAR, 45 | 'notNull' => true, 46 | 'size' => 30, 47 | 'after' => 'name' 48 | ] 49 | ), 50 | new Column( 51 | 'address', 52 | [ 53 | 'type' => Column::TYPE_VARCHAR, 54 | 'notNull' => true, 55 | 'size' => 40, 56 | 'after' => 'telephone' 57 | ] 58 | ), 59 | new Column( 60 | 'city', 61 | [ 62 | 'type' => Column::TYPE_VARCHAR, 63 | 'notNull' => true, 64 | 'size' => 40, 65 | 'after' => 'address' 66 | ] 67 | ) 68 | ], 69 | 'indexes' => [ 70 | new Index('PRIMARY', ['id'], 'PRIMARY') 71 | ], 72 | 'options' => [ 73 | 'TABLE_TYPE' => 'BASE TABLE', 74 | 'AUTO_INCREMENT' => '3', 75 | 'ENGINE' => 'InnoDB', 76 | 'TABLE_COLLATION' => 'utf8_unicode_ci' 77 | ], 78 | ] 79 | ); 80 | } 81 | 82 | /** 83 | * Run the migrations 84 | * 85 | * @return void 86 | */ 87 | public function up() 88 | { 89 | $this->batchInsert('companies', [ 90 | 'id', 91 | 'name', 92 | 'telephone', 93 | 'address', 94 | 'city' 95 | ] 96 | ); 97 | } 98 | 99 | /** 100 | * Reverse the migrations 101 | * 102 | * @return void 103 | */ 104 | public function down() 105 | { 106 | $this->batchDelete('companies'); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /db/migrations/1.0.0/contact.php: -------------------------------------------------------------------------------- 1 | morphTable('contact', [ 20 | 'columns' => [ 21 | new Column( 22 | 'id', 23 | [ 24 | 'type' => Column::TYPE_INTEGER, 25 | 'unsigned' => true, 26 | 'notNull' => true, 27 | 'autoIncrement' => true, 28 | 'size' => 10, 29 | 'first' => true 30 | ] 31 | ), 32 | new Column( 33 | 'name', 34 | [ 35 | 'type' => Column::TYPE_VARCHAR, 36 | 'notNull' => true, 37 | 'size' => 70, 38 | 'after' => 'id' 39 | ] 40 | ), 41 | new Column( 42 | 'email', 43 | [ 44 | 'type' => Column::TYPE_VARCHAR, 45 | 'notNull' => true, 46 | 'size' => 70, 47 | 'after' => 'name' 48 | ] 49 | ), 50 | new Column( 51 | 'comments', 52 | [ 53 | 'type' => Column::TYPE_TEXT, 54 | 'notNull' => true, 55 | 'size' => 1, 56 | 'after' => 'email' 57 | ] 58 | ), 59 | new Column( 60 | 'created_at', 61 | [ 62 | 'type' => Column::TYPE_TIMESTAMP, 63 | 'default' => "CURRENT_TIMESTAMP(1)", 64 | 'notNull' => true, 65 | 'size' => 1, 66 | 'after' => 'comments' 67 | ] 68 | ) 69 | ], 70 | 'indexes' => [ 71 | new Index('PRIMARY', ['id'], 'PRIMARY') 72 | ], 73 | 'options' => [ 74 | 'TABLE_TYPE' => 'BASE TABLE', 75 | 'AUTO_INCREMENT' => '1', 76 | 'ENGINE' => 'InnoDB', 77 | 'TABLE_COLLATION' => 'utf8_unicode_ci' 78 | ], 79 | ] 80 | ); 81 | } 82 | 83 | /** 84 | * Run the migrations 85 | * 86 | * @return void 87 | */ 88 | public function up() 89 | { 90 | 91 | } 92 | 93 | /** 94 | * Reverse the migrations 95 | * 96 | * @return void 97 | */ 98 | public function down() 99 | { 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /db/migrations/1.0.0/product_types.dat: -------------------------------------------------------------------------------- 1 | 5,Vegetables 2 | 6,Fruits 3 | -------------------------------------------------------------------------------- /db/migrations/1.0.0/product_types.php: -------------------------------------------------------------------------------- 1 | morphTable('product_types', [ 20 | 'columns' => [ 21 | new Column( 22 | 'id', 23 | [ 24 | 'type' => Column::TYPE_INTEGER, 25 | 'unsigned' => true, 26 | 'notNull' => true, 27 | 'autoIncrement' => true, 28 | 'size' => 10, 29 | 'first' => true 30 | ] 31 | ), 32 | new Column( 33 | 'name', 34 | [ 35 | 'type' => Column::TYPE_VARCHAR, 36 | 'notNull' => true, 37 | 'size' => 70, 38 | 'after' => 'id' 39 | ] 40 | ) 41 | ], 42 | 'indexes' => [ 43 | new Index('PRIMARY', ['id'], 'PRIMARY') 44 | ], 45 | 'options' => [ 46 | 'TABLE_TYPE' => 'BASE TABLE', 47 | 'AUTO_INCREMENT' => '7', 48 | 'ENGINE' => 'InnoDB', 49 | 'TABLE_COLLATION' => 'utf8_unicode_ci' 50 | ], 51 | ] 52 | ); 53 | } 54 | 55 | /** 56 | * Run the migrations 57 | * 58 | * @return void 59 | */ 60 | public function up() 61 | { 62 | $this->batchInsert('product_types', [ 63 | 'id', 64 | 'name' 65 | ] 66 | ); 67 | } 68 | 69 | /** 70 | * Reverse the migrations 71 | * 72 | * @return void 73 | */ 74 | public function down() 75 | { 76 | $this->batchDelete('product_types'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /db/migrations/1.0.0/products.dat: -------------------------------------------------------------------------------- 1 | 1,5,Artichoke,10.50,0 2 | 2,5,"Bell pepper",10.40,0 3 | 3,5,Cauliflower,20.10,0 4 | 4,5,"Chinese cabbage",15.50,0 5 | 5,5,"Malabar spinach",7.50,0 6 | 6,5,Onion,3.50,0 7 | 7,5,Peanut,4.50,0 8 | -------------------------------------------------------------------------------- /db/migrations/1.0.0/products.php: -------------------------------------------------------------------------------- 1 | morphTable('products', [ 21 | 'columns' => [ 22 | new Column( 23 | 'id', 24 | [ 25 | 'type' => Column::TYPE_INTEGER, 26 | 'unsigned' => true, 27 | 'notNull' => true, 28 | 'autoIncrement' => true, 29 | 'size' => 10, 30 | 'first' => true 31 | ] 32 | ), 33 | new Column( 34 | 'product_types_id', 35 | [ 36 | 'type' => Column::TYPE_INTEGER, 37 | 'unsigned' => true, 38 | 'notNull' => true, 39 | 'size' => 10, 40 | 'after' => 'id' 41 | ] 42 | ), 43 | new Column( 44 | 'name', 45 | [ 46 | 'type' => Column::TYPE_VARCHAR, 47 | 'notNull' => true, 48 | 'size' => 70, 49 | 'after' => 'product_types_id' 50 | ] 51 | ), 52 | new Column( 53 | 'price', 54 | [ 55 | 'type' => Column::TYPE_DECIMAL, 56 | 'notNull' => true, 57 | 'size' => 16, 58 | 'scale' => 2, 59 | 'after' => 'name' 60 | ] 61 | ), 62 | new Column( 63 | 'active', 64 | [ 65 | 'type' => Column::TYPE_TINYINTEGER, 66 | 'size' => 1, 67 | 'default' => Status::INACTIVE, 68 | 'after' => 'price' 69 | ] 70 | ) 71 | ], 72 | 'indexes' => [ 73 | new Index('PRIMARY', ['id'], 'PRIMARY') 74 | ], 75 | 'options' => [ 76 | 'TABLE_TYPE' => 'BASE TABLE', 77 | 'AUTO_INCREMENT' => '8', 78 | 'ENGINE' => 'InnoDB', 79 | 'TABLE_COLLATION' => 'utf8_unicode_ci' 80 | ], 81 | ] 82 | ); 83 | } 84 | 85 | /** 86 | * Run the migrations 87 | * 88 | * @return void 89 | */ 90 | public function up() 91 | { 92 | $this->batchInsert('products', [ 93 | 'id', 94 | 'product_types_id', 95 | 'name', 96 | 'price', 97 | 'active' 98 | ] 99 | ); 100 | } 101 | 102 | /** 103 | * Reverse the migrations 104 | * 105 | * @return void 106 | */ 107 | public function down() 108 | { 109 | $this->batchDelete('products'); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /db/migrations/1.0.0/users.dat: -------------------------------------------------------------------------------- 1 | 1,demo,c0bd96dc7ea4ec56741a4e07f6ce98012814d853,"Phalcon Demo",demo@phalcon.io,"2012-04-10 20:53:03",1 2 | -------------------------------------------------------------------------------- /db/migrations/1.0.0/users.php: -------------------------------------------------------------------------------- 1 | morphTable('users', [ 21 | 'columns' => [ 22 | new Column( 23 | 'id', 24 | [ 25 | 'type' => Column::TYPE_INTEGER, 26 | 'unsigned' => true, 27 | 'notNull' => true, 28 | 'autoIncrement' => true, 29 | 'size' => 10, 30 | 'first' => true 31 | ] 32 | ), 33 | new Column( 34 | 'username', 35 | [ 36 | 'type' => Column::TYPE_VARCHAR, 37 | 'notNull' => true, 38 | 'size' => 32, 39 | 'after' => 'id' 40 | ] 41 | ), 42 | new Column( 43 | 'password', 44 | [ 45 | 'type' => Column::TYPE_CHAR, 46 | 'notNull' => true, 47 | 'size' => 40, 48 | 'after' => 'username' 49 | ] 50 | ), 51 | new Column( 52 | 'name', 53 | [ 54 | 'type' => Column::TYPE_VARCHAR, 55 | 'notNull' => true, 56 | 'size' => 120, 57 | 'after' => 'password' 58 | ] 59 | ), 60 | new Column( 61 | 'email', 62 | [ 63 | 'type' => Column::TYPE_VARCHAR, 64 | 'notNull' => true, 65 | 'size' => 70, 66 | 'after' => 'name' 67 | ] 68 | ), 69 | new Column( 70 | 'created_at', 71 | [ 72 | 'type' => Column::TYPE_TIMESTAMP, 73 | 'default' => "CURRENT_TIMESTAMP(1)", 74 | 'notNull' => true, 75 | 'size' => 1, 76 | 'after' => 'email' 77 | ] 78 | ), 79 | new Column( 80 | 'active', 81 | [ 82 | 'type' => Column::TYPE_TINYINTEGER, 83 | 'notNull' => true, 84 | 'size' => 1, 85 | 'default' => Status::INACTIVE, 86 | 'after' => 'created_at' 87 | ] 88 | ) 89 | ], 90 | 'indexes' => [ 91 | new Index('PRIMARY', ['id'], 'PRIMARY') 92 | ], 93 | 'options' => [ 94 | 'TABLE_TYPE' => 'BASE TABLE', 95 | 'AUTO_INCREMENT' => '2', 96 | 'ENGINE' => 'InnoDB', 97 | 'TABLE_COLLATION' => 'utf8_unicode_ci' 98 | ], 99 | ] 100 | ); 101 | } 102 | 103 | /** 104 | * Run the migrations 105 | * 106 | * @return void 107 | */ 108 | public function up() 109 | { 110 | $this->batchInsert('users', [ 111 | 'id', 112 | 'username', 113 | 'password', 114 | 'name', 115 | 'email', 116 | 'created_at', 117 | 'active' 118 | ] 119 | ); 120 | } 121 | 122 | /** 123 | * Reverse the migrations 124 | * 125 | * @return void 126 | */ 127 | public function down() 128 | { 129 | $this->batchDelete('users'); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # For local development only. 2 | 3 | version: '3' 4 | 5 | services: 6 | invo-8.0: 7 | container_name: invo-8.0 8 | hostname: invo-80 9 | build: docker/8.0 10 | working_dir: /srv 11 | volumes: 12 | - .:/srv 13 | ports: 14 | - 80:80 15 | 16 | invo-8.1: 17 | container_name: invo-8.1 18 | hostname: invo-81 19 | build: docker/8.1 20 | working_dir: /srv 21 | volumes: 22 | - .:/srv 23 | ports: 24 | - 81:80 25 | 26 | mysql: 27 | container_name: invo-mysql 28 | hostname: invo-mysql 29 | image: mysql:5.7 30 | environment: 31 | - MYSQL_ROOT_PASSWORD=secret 32 | - MYSQL_USER=phalcon 33 | - MYSQL_DATABASE=phalcon_invo 34 | - MYSQL_PASSWORD=secret 35 | ports: 36 | - 3306:3306 37 | -------------------------------------------------------------------------------- /docker/8.0/.bashrc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Easier navigation: .., ..., ...., ....., ~ and - 4 | alias ..="cd .." 5 | alias ...="cd ../.." 6 | alias ....="cd ../../.." 7 | alias .....="cd ../../../.." 8 | alias ~="cd ~" # `cd` is probably faster to type though 9 | alias -- -="cd -" 10 | 11 | # Shortcuts 12 | alias g="git" 13 | alias h="history" 14 | 15 | # Detect which `ls` flavor is in use 16 | if ls --color > /dev/null 2>&1; then # GNU `ls` 17 | colorflag="--color" 18 | else # OS X `ls` 19 | colorflag="-G" 20 | fi 21 | 22 | # List all files colorized in long format 23 | # shellcheck disable=SC2139 24 | alias l="ls -lF ${colorflag}" 25 | 26 | # List all files colorized in long format, including dot files 27 | # shellcheck disable=SC2139 28 | alias la="ls -laF ${colorflag}" 29 | 30 | # List only directories 31 | # shellcheck disable=SC2139 32 | alias lsd="ls -lF ${colorflag} | grep --color=never '^d'" 33 | 34 | # See: https://superuser.com/a/656746/280737 35 | alias ll='LC_ALL="C.UTF-8" ls -alF' 36 | 37 | # Always use color output for `ls` 38 | # shellcheck disable=SC2139 39 | alias ls="command ls ${colorflag}" 40 | export LS_COLORS='no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:' 41 | 42 | # Always enable colored `grep` output 43 | alias grep='grep --color=auto ' 44 | 45 | # Enable aliases to be sudo’ed 46 | alias sudo='sudo ' 47 | 48 | # Get week number 49 | alias week='date +%V' 50 | 51 | # Stopwatch 52 | alias timer='echo "Timer started. Stop with Ctrl-D." && date && time cat && date' 53 | 54 | # Canonical hex dump; some systems have this symlinked 55 | command -v hd > /dev/null || alias hd="hexdump -C" 56 | 57 | # vhosts 58 | alias hosts='sudo nano /etc/hosts' 59 | 60 | # copy working directory 61 | alias cwd='pwd | tr -d "\r\n" | xclip -selection clipboard' 62 | 63 | # copy file interactive 64 | alias cp='cp -i' 65 | 66 | # move file interactive 67 | alias mv='mv -i' 68 | 69 | # untar 70 | alias untar='tar xvf' 71 | 72 | # Zephir related 73 | alias untar='tar xvf' 74 | 75 | PATH=$PATH:./vendor/bin 76 | -------------------------------------------------------------------------------- /docker/8.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM composer:latest as composer 2 | FROM php:8.0-cli 3 | 4 | LABEL vendor="Phalcon" \ 5 | maintainer="Phalcon Team " \ 6 | description="The PHP image to test Invo example concepts" 7 | 8 | ENV PHALCON_VERSION="5.1.4" \ 9 | PHP_VERSION="8.0" 10 | 11 | ADD ./extra.ini /usr/local/etc/php/conf.d/ 12 | ADD . /code 13 | 14 | WORKDIR /code 15 | 16 | # Update 17 | RUN apt update -y && \ 18 | apt install -y \ 19 | apt-utils \ 20 | gettext \ 21 | git \ 22 | libzip-dev \ 23 | nano \ 24 | sudo \ 25 | wget \ 26 | zip 27 | 28 | # PECL Packages 29 | RUN pecl install phalcon-${PHALCON_VERSION} \ 30 | xdebug 31 | 32 | # Install PHP extensions 33 | RUN docker-php-ext-install \ 34 | gettext \ 35 | pdo_mysql \ 36 | zip 37 | 38 | # Install PHP extensions 39 | RUN docker-php-ext-enable \ 40 | opcache \ 41 | phalcon \ 42 | xdebug 43 | 44 | # Cleanup 45 | RUN apt autoremove -y \ 46 | && apt autoclean -y \ 47 | && apt clean -y \ 48 | && rm -rf /tmp/* /var/tmp/* \ 49 | && find /var/cache/apt/archives /var/lib/apt/lists /var/cache \ 50 | -not -name lock \ 51 | -type f \ 52 | -delete \ 53 | && find /var/log -type f | while read f; do echo -n '' > ${f}; done 54 | 55 | RUN php -m | grep -i "opcache\|mysql\|phalcon\||pdo\|mbstring" 56 | # && mv /code/.env.example /code/.env 57 | 58 | # Composer 59 | COPY --from=composer /usr/bin/composer /usr/local/bin/composer 60 | # Bash script with helper aliases 61 | COPY ./.bashrc /root/.bashrc 62 | COPY ./.bashrc /home/phalcon/.bashrc 63 | 64 | EXPOSE 80 65 | 66 | # docker run -p 80:80 phalconphp/vokuro:4.1.2 67 | CMD ["php", "-S", "0.0.0.0:80", "-t", "public/", ".htrouter.php"] 68 | -------------------------------------------------------------------------------- /docker/8.0/extra.ini: -------------------------------------------------------------------------------- 1 | error_reporting=E_ALL 2 | display_errors="On" 3 | display_startup_errors="On" 4 | log_errors="On" 5 | error_log=/tmp/php_errors.log 6 | memory_limit=512M 7 | apc.enable_cli="On" 8 | session.save_path="/tmp" 9 | xdebug.mode="coverage" 10 | -------------------------------------------------------------------------------- /docker/8.1/.bashrc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Easier navigation: .., ..., ...., ....., ~ and - 4 | alias ..="cd .." 5 | alias ...="cd ../.." 6 | alias ....="cd ../../.." 7 | alias .....="cd ../../../.." 8 | alias ~="cd ~" # `cd` is probably faster to type though 9 | alias -- -="cd -" 10 | 11 | # Shortcuts 12 | alias g="git" 13 | alias h="history" 14 | 15 | # Detect which `ls` flavor is in use 16 | if ls --color > /dev/null 2>&1; then # GNU `ls` 17 | colorflag="--color" 18 | else # OS X `ls` 19 | colorflag="-G" 20 | fi 21 | 22 | # List all files colorized in long format 23 | # shellcheck disable=SC2139 24 | alias l="ls -lF ${colorflag}" 25 | 26 | # List all files colorized in long format, including dot files 27 | # shellcheck disable=SC2139 28 | alias la="ls -laF ${colorflag}" 29 | 30 | # List only directories 31 | # shellcheck disable=SC2139 32 | alias lsd="ls -lF ${colorflag} | grep --color=never '^d'" 33 | 34 | # See: https://superuser.com/a/656746/280737 35 | alias ll='LC_ALL="C.UTF-8" ls -alF' 36 | 37 | # Always use color output for `ls` 38 | # shellcheck disable=SC2139 39 | alias ls="command ls ${colorflag}" 40 | export LS_COLORS='no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:' 41 | 42 | # Always enable colored `grep` output 43 | alias grep='grep --color=auto ' 44 | 45 | # Enable aliases to be sudo’ed 46 | alias sudo='sudo ' 47 | 48 | # Get week number 49 | alias week='date +%V' 50 | 51 | # Stopwatch 52 | alias timer='echo "Timer started. Stop with Ctrl-D." && date && time cat && date' 53 | 54 | # Canonical hex dump; some systems have this symlinked 55 | command -v hd > /dev/null || alias hd="hexdump -C" 56 | 57 | # vhosts 58 | alias hosts='sudo nano /etc/hosts' 59 | 60 | # copy working directory 61 | alias cwd='pwd | tr -d "\r\n" | xclip -selection clipboard' 62 | 63 | # copy file interactive 64 | alias cp='cp -i' 65 | 66 | # move file interactive 67 | alias mv='mv -i' 68 | 69 | # untar 70 | alias untar='tar xvf' 71 | 72 | # Zephir related 73 | alias untar='tar xvf' 74 | 75 | PATH=$PATH:./vendor/bin 76 | -------------------------------------------------------------------------------- /docker/8.1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM composer:latest as composer 2 | FROM php:8.1-cli 3 | 4 | LABEL vendor="Phalcon" \ 5 | maintainer="Phalcon Team " \ 6 | description="The PHP image to test Invo example concepts" 7 | 8 | ENV PHALCON_VERSION="5.1.4" \ 9 | PHP_VERSION="8.1" 10 | 11 | ADD ./extra.ini /usr/local/etc/php/conf.d/ 12 | ADD . /code 13 | 14 | WORKDIR /code 15 | 16 | # Update 17 | RUN apt update -y && \ 18 | apt install -y \ 19 | apt-utils \ 20 | gettext \ 21 | git \ 22 | libzip-dev \ 23 | nano \ 24 | sudo \ 25 | wget \ 26 | zip 27 | 28 | # PECL Packages 29 | RUN pecl install phalcon-${PHALCON_VERSION} \ 30 | xdebug 31 | 32 | # Install PHP extensions 33 | RUN docker-php-ext-install \ 34 | gettext \ 35 | pdo_mysql \ 36 | zip 37 | 38 | # Install PHP extensions 39 | RUN docker-php-ext-enable \ 40 | opcache \ 41 | phalcon \ 42 | xdebug 43 | 44 | # Cleanup 45 | RUN apt autoremove -y \ 46 | && apt autoclean -y \ 47 | && apt clean -y \ 48 | && rm -rf /tmp/* /var/tmp/* \ 49 | && find /var/cache/apt/archives /var/lib/apt/lists /var/cache \ 50 | -not -name lock \ 51 | -type f \ 52 | -delete \ 53 | && find /var/log -type f | while read f; do echo -n '' > ${f}; done 54 | 55 | RUN php -m | grep -i "opcache\|mysql\|phalcon\||pdo\|mbstring" 56 | # && mv /code/.env.example /code/.env 57 | 58 | # Composer 59 | COPY --from=composer /usr/bin/composer /usr/local/bin/composer 60 | # Bash script with helper aliases 61 | COPY ./.bashrc /root/.bashrc 62 | COPY ./.bashrc /home/phalcon/.bashrc 63 | 64 | EXPOSE 80 65 | 66 | # docker run -p 80:80 phalconphp/vokuro:4.1.2 67 | CMD ["php", "-S", "0.0.0.0:80", "-t", "public/", ".htrouter.php"] 68 | -------------------------------------------------------------------------------- /docker/8.1/extra.ini: -------------------------------------------------------------------------------- 1 | error_reporting=E_ALL 2 | display_errors="On" 3 | display_startup_errors="On" 4 | log_errors="On" 5 | error_log=/tmp/php_errors.log 6 | memory_limit=512M 7 | apc.enable_cli="On" 8 | session.save_path="/tmp" 9 | xdebug.mode="coverage" 10 | -------------------------------------------------------------------------------- /migrations.php: -------------------------------------------------------------------------------- 1 | load() 9 | ; 10 | 11 | return new Config( 12 | [ 13 | 'database' => [ 14 | 'adapter' => $_ENV['DB_ADAPTER'] ?? 'Mysql', 15 | 'host' => $_ENV['DB_HOST'] ?? 'localhost', 16 | 'username' => $_ENV['DB_USERNAME'] ?? 'phalcon', 17 | 'password' => $_ENV['DB_PASSWORD'] ?? 'secret', 18 | 'dbname' => $_ENV['DB_DBNAME'] ?? 'phalcon_invo', 19 | 'charset' => $_ENV['DB_CHARSET'] ?? 'utf8', 20 | ], 21 | 'application' => [ 22 | 'logInDb' => true, 23 | 'migrationsDir' => 'db/migrations', 24 | 'exportDataFromTables' => [ 25 | 'companies', 26 | 'product_types', 27 | 'products', 28 | 'users', 29 | ], 30 | ], 31 | ] 32 | ); 33 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Phalcon Coding Standards 4 | 5 | 6 | 7 | 8 | 9 | 10 | src 11 | public/index.php 12 | tests/acceptance 13 | tests/functional 14 | tests/unit 15 | 16 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | AddDefaultCharset UTF-8 2 | 3 | 4 | RewriteEngine On 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | 8 | # DocumentRoot 9 | RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L] 10 | 11 | # VirtualDocumentRoot 12 | # RewriteRule ^(.*)$ /index.php?_url=/$1 [QSA,L] 13 | 14 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phalcon/invo/14da7fb8b60c800f16362d053ba38a55dd7ecb74/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | load() 18 | ; 19 | 20 | /** 21 | * Init Phalcon Dependency Injection 22 | */ 23 | $di = new FactoryDefault(); 24 | $di->offsetSet('rootPath', function () use ($rootPath) { 25 | return $rootPath; 26 | }); 27 | 28 | /** 29 | * Register Service Providers 30 | */ 31 | $providers = $rootPath . '/config/providers.php'; 32 | if (!file_exists($providers) || !is_readable($providers)) { 33 | throw new Exception('File providers.php does not exist or is not readable.'); 34 | } 35 | 36 | /** @var array $providers */ 37 | $providers = include_once $providers; 38 | foreach ($providers as $provider) { 39 | $di->register(new $provider()); 40 | } 41 | 42 | /** 43 | * Init MVC Application and send output to client 44 | */ 45 | (new Application($di)) 46 | ->handle($_SERVER['REQUEST_URI']) 47 | ->send() 48 | ; 49 | } catch (Exception $e) { 50 | echo $e->getMessage() . '
'; 51 | echo '
' . $e->getTraceAsString() . '
'; 52 | } 53 | -------------------------------------------------------------------------------- /public/js/utils.js: -------------------------------------------------------------------------------- 1 | let Profile = { 2 | check: function (id) { 3 | if ($.trim($("#" + id)[0].value) === '') { 4 | $("#" + id)[0].focus(); 5 | $("#" + id + "_alert").show(); 6 | 7 | return false; 8 | } 9 | 10 | return true; 11 | }, 12 | validate: function () { 13 | if (Profile.check("name") === false) { 14 | return false; 15 | } 16 | if (Profile.check("email") === false) { 17 | return false; 18 | } 19 | $("#profileForm")[0].submit(); 20 | } 21 | }; 22 | 23 | var SignUp = { 24 | check: function (id) { 25 | if ($.trim($("#" + id)[0].value) === '') { 26 | $("#" + id)[0].focus(); 27 | $("#" + id + "_alert").show(); 28 | 29 | return false; 30 | } 31 | 32 | return true; 33 | }, 34 | validate: function () { 35 | if (SignUp.check("name") === false) { 36 | return false; 37 | } 38 | if (SignUp.check("username") === false) { 39 | return false; 40 | } 41 | if (SignUp.check("email") === false) { 42 | return false; 43 | } 44 | if (SignUp.check("password") === false) { 45 | return false; 46 | } 47 | if ($("#password")[0].value !== $("#repeatPassword")[0].value) { 48 | $("#repeatPassword")[0].focus(); 49 | $("#repeatPassword_alert").show(); 50 | 51 | return false; 52 | } 53 | $("#registerForm")[0].submit(); 54 | } 55 | }; 56 | 57 | $(document).ready(function () { 58 | $("#registerForm .alert").hide(); 59 | $("#profileForm .alert").hide(); 60 | }); 61 | -------------------------------------------------------------------------------- /src/Constants/Status.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Constants; 15 | 16 | class Status 17 | { 18 | public const ACTIVE = 1; 19 | public const INACTIVE = 0; 20 | } 21 | -------------------------------------------------------------------------------- /src/Controllers/AboutController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | class AboutController extends ControllerBase 17 | { 18 | public function initialize() 19 | { 20 | parent::initialize(); 21 | 22 | $this->tag->title() 23 | ->set('About us') 24 | ; 25 | } 26 | 27 | public function indexAction(): void 28 | { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Controllers/CompaniesController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Invo\Forms\CompaniesForm; 17 | use Invo\Models\Companies; 18 | use Phalcon\Mvc\Model\Criteria; 19 | use Phalcon\Paginator\Adapter\Model as Paginator; 20 | 21 | class CompaniesController extends ControllerBase 22 | { 23 | public function initialize() 24 | { 25 | parent::initialize(); 26 | 27 | $this->tag->title() 28 | ->set('Manage your companies') 29 | ; 30 | } 31 | 32 | /** 33 | * Shows the index action 34 | */ 35 | public function indexAction(): void 36 | { 37 | $this->view->form = new CompaniesForm(); 38 | } 39 | 40 | /** 41 | * Search companies based on current criteria 42 | */ 43 | public function searchAction(): void 44 | { 45 | if ($this->request->isPost()) { 46 | $query = Criteria::fromInput( 47 | $this->di, 48 | Companies::class, 49 | $this->request->getPost() 50 | ); 51 | 52 | $this->persistent->searchParams = ['di' => null] + $query->getParams(); 53 | } 54 | 55 | $parameters = []; 56 | if ($this->persistent->searchParams) { 57 | $parameters = $this->persistent->searchParams; 58 | } 59 | 60 | $companies = Companies::find($parameters); 61 | if (count($companies) == 0) { 62 | $this->flash->notice('The search did not find any companies'); 63 | 64 | $this->dispatcher->forward([ 65 | 'controller' => 'companies', 66 | 'action' => 'index', 67 | ]); 68 | 69 | return; 70 | } 71 | 72 | $paginator = new Paginator([ 73 | 'model' => Companies::class, 74 | 'parameters' => $parameters, 75 | 'limit' => 10, 76 | 'page' => $this->request->getQuery('page', 'int', 1), 77 | ]); 78 | 79 | $this->view->page = $paginator->paginate(); 80 | $this->view->companies = $companies; 81 | } 82 | 83 | /** 84 | * Shows the form to create a new company 85 | */ 86 | public function newAction(): void 87 | { 88 | $this->view->form = new CompaniesForm(null, ['edit' => true]); 89 | } 90 | 91 | /** 92 | * Edits a company based on its id 93 | * 94 | * @param int $id 95 | */ 96 | public function editAction($id): void 97 | { 98 | $company = Companies::findFirstById($id); 99 | if (!$company) { 100 | $this->flash->error('Company was not found'); 101 | 102 | $this->dispatcher->forward([ 103 | 'controller' => 'companies', 104 | 'action' => 'index', 105 | ]); 106 | 107 | return; 108 | } 109 | 110 | $this->view->form = new CompaniesForm($company, ['edit' => true]); 111 | } 112 | 113 | /** 114 | * Creates a new company 115 | */ 116 | public function createAction(): void 117 | { 118 | if (!$this->request->isPost()) { 119 | $this->dispatcher->forward([ 120 | 'controller' => 'companies', 121 | 'action' => 'index', 122 | ]); 123 | 124 | return; 125 | } 126 | 127 | $form = new CompaniesForm(); 128 | $company = new Companies(); 129 | 130 | $data = $this->request->getPost(); 131 | if (!$form->isValid($data, $company)) { 132 | foreach ($form->getMessages() as $message) { 133 | $this->flash->error((string) $message); 134 | } 135 | 136 | $this->dispatcher->forward([ 137 | 'controller' => 'companies', 138 | 'action' => 'new', 139 | ]); 140 | 141 | return; 142 | } 143 | 144 | if (!$company->save()) { 145 | foreach ($company->getMessages() as $message) { 146 | $this->flash->error((string) $message); 147 | } 148 | 149 | $this->dispatcher->forward([ 150 | 'controller' => 'companies', 151 | 'action' => 'new', 152 | ]); 153 | 154 | return; 155 | } 156 | 157 | $form->clear(); 158 | $this->flash->success('Company was created successfully'); 159 | 160 | $this->dispatcher->forward([ 161 | 'controller' => 'companies', 162 | 'action' => 'index', 163 | ]); 164 | } 165 | 166 | /** 167 | * Saves current company in screen 168 | */ 169 | public function saveAction(): void 170 | { 171 | if (!$this->request->isPost()) { 172 | $this->dispatcher->forward([ 173 | 'controller' => 'companies', 174 | 'action' => 'index', 175 | ]); 176 | 177 | return; 178 | } 179 | 180 | $id = $this->request->getPost('id', 'int'); 181 | $company = Companies::findFirstById($id); 182 | if (!$company) { 183 | $this->flash->error('Company does not exist'); 184 | 185 | $this->dispatcher->forward([ 186 | 'controller' => 'companies', 187 | 'action' => 'index', 188 | ]); 189 | 190 | return; 191 | } 192 | 193 | $data = $this->request->getPost(); 194 | $form = new CompaniesForm(); 195 | if (!$form->isValid($data, $company)) { 196 | foreach ($form->getMessages() as $message) { 197 | $this->flash->error((string) $message); 198 | } 199 | 200 | $this->dispatcher->forward([ 201 | 'controller' => 'companies', 202 | 'action' => 'new', 203 | ]); 204 | 205 | return; 206 | } 207 | 208 | if (!$company->save()) { 209 | foreach ($company->getMessages() as $message) { 210 | $this->flash->error((string) $message); 211 | } 212 | 213 | $this->dispatcher->forward([ 214 | 'controller' => 'companies', 215 | 'action' => 'new', 216 | ]); 217 | 218 | return; 219 | } 220 | 221 | $form->clear(); 222 | $this->flash->success('Company was updated successfully'); 223 | 224 | $this->dispatcher->forward([ 225 | 'controller' => 'companies', 226 | 'action' => 'index', 227 | ]); 228 | } 229 | 230 | /** 231 | * Deletes a company 232 | * 233 | * @param string $id 234 | */ 235 | public function deleteAction($id) 236 | { 237 | $companies = Companies::findFirstById($id); 238 | if (!$companies) { 239 | $this->flash->error('Company was not found'); 240 | 241 | $this->dispatcher->forward([ 242 | 'controller' => 'companies', 243 | 'action' => 'index', 244 | ]); 245 | 246 | return; 247 | } 248 | 249 | if (!$companies->delete()) { 250 | foreach ($companies->getMessages() as $message) { 251 | $this->flash->error((string) $message); 252 | } 253 | 254 | $this->dispatcher->forward([ 255 | 'controller' => 'companies', 256 | 'action' => 'search', 257 | ]); 258 | 259 | return; 260 | } 261 | 262 | $this->flash->success('Company was deleted'); 263 | 264 | $this->dispatcher->forward([ 265 | 'controller' => 'companies', 266 | 'action' => 'index', 267 | ]); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/Controllers/ContactController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Invo\Forms\ContactForm; 17 | use Invo\Models\Contact; 18 | 19 | /** 20 | * ContactController 21 | * 22 | * Allows to contact the staff using a contact form 23 | */ 24 | class ContactController extends ControllerBase 25 | { 26 | public function initialize() 27 | { 28 | parent::initialize(); 29 | 30 | $this->tag->title() 31 | ->set('Contact us') 32 | ; 33 | } 34 | 35 | public function indexAction(): void 36 | { 37 | $this->view->form = new ContactForm(); 38 | } 39 | 40 | /** 41 | * Saves the contact information in the database 42 | */ 43 | public function sendAction(): void 44 | { 45 | if (!$this->request->isPost()) { 46 | $this->dispatcher->forward([ 47 | 'controller' => 'contact', 48 | 'action' => 'index', 49 | ]); 50 | 51 | return; 52 | } 53 | 54 | $form = new ContactForm(); 55 | $contact = new Contact(); 56 | 57 | // Validate the form 58 | if (!$form->isValid($this->request->getPost(), $contact)) { 59 | foreach ($form->getMessages() as $message) { 60 | $this->flash->error((string) $message); 61 | } 62 | 63 | $this->dispatcher->forward([ 64 | 'controller' => 'contact', 65 | 'action' => 'index', 66 | ]); 67 | 68 | return; 69 | } 70 | 71 | if (!$contact->save()) { 72 | foreach ($contact->getMessages() as $message) { 73 | $this->flash->error((string) $message); 74 | } 75 | 76 | $this->dispatcher->forward([ 77 | 'controller' => 'contact', 78 | 'action' => 'index', 79 | ]); 80 | 81 | return; 82 | } 83 | 84 | $this->flash->success('Thanks, we will contact you in the next few hours'); 85 | 86 | $this->dispatcher->forward([ 87 | 'controller' => 'index', 88 | 'action' => 'index', 89 | ]); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Controllers/ControllerBase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Phalcon\Mvc\Controller; 17 | 18 | class ControllerBase extends Controller 19 | { 20 | protected function initialize() 21 | { 22 | $this->tag->title() 23 | ->prepend('INVO | ') 24 | ; 25 | $this->view->setTemplateAfter('main'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Controllers/ErrorsController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | /** 17 | * ErrorsController 18 | * 19 | * Manage errors 20 | */ 21 | class ErrorsController extends ControllerBase 22 | { 23 | public function initialize() 24 | { 25 | $this->tag->title() 26 | ->set('Oops!') 27 | ; 28 | 29 | parent::initialize(); 30 | } 31 | 32 | public function show404Action(): void 33 | { 34 | $this->response->setStatusCode(404); 35 | } 36 | 37 | public function show401Action(): void 38 | { 39 | $this->response->setStatusCode(401); 40 | } 41 | 42 | public function show500Action(): void 43 | { 44 | $this->response->setStatusCode(500); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Controllers/IndexController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | class IndexController extends ControllerBase 17 | { 18 | public function initialize() 19 | { 20 | parent::initialize(); 21 | 22 | $this->tag->title() 23 | ->set('Welcome') 24 | ; 25 | } 26 | 27 | public function indexAction(): void 28 | { 29 | $this->flash->notice( 30 | 'This is a sample application of the Phalcon Framework. 31 | Please don\'t provide us any personal information. Thanks' 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Controllers/InvoicesController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Invo\Forms\ProfileForm; 17 | use Invo\Models\Users; 18 | 19 | /** 20 | * InvoicesController 21 | * 22 | * Manage operations for invoices 23 | */ 24 | class InvoicesController extends ControllerBase 25 | { 26 | public function initialize() 27 | { 28 | $this->tag->title() 29 | ->set('Manage your Invoices') 30 | ; 31 | 32 | parent::initialize(); 33 | } 34 | 35 | public function indexAction(): void 36 | { 37 | } 38 | 39 | /** 40 | * Edit the active user profile 41 | */ 42 | public function profileAction(): void 43 | { 44 | //Get session info 45 | $auth = $this->session->get('auth'); 46 | 47 | //Query the active user 48 | $user = Users::findFirst($auth['id']); 49 | if (!$user) { 50 | $this->dispatcher->forward([ 51 | 'controller' => 'index', 52 | 'action' => 'index', 53 | ]); 54 | 55 | return; 56 | } 57 | 58 | // Pass user to fill the form with the user data 59 | $form = new ProfileForm($user); 60 | 61 | if ($this->request->isPost()) { 62 | $data = $this->request->getPost(); 63 | 64 | if ($form->isValid($data, $user)) { 65 | if (!$user->save()) { 66 | foreach ($user->getMessages() as $message) { 67 | $this->flash->error((string) $message); 68 | } 69 | } else { 70 | $this->flash->success('Your profile information was updated successfully'); 71 | } 72 | } else { 73 | foreach ($form->getMessages() as $message) { 74 | $this->flash->error((string) $message); 75 | } 76 | } 77 | } 78 | 79 | $this->view->form = $form; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Controllers/ProductsController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Invo\Constants\Status; 17 | use Invo\Forms\ProductsForm; 18 | use Invo\Models\Products; 19 | use Phalcon\Mvc\Model\Criteria; 20 | use Phalcon\Paginator\Adapter\Model as Paginator; 21 | 22 | /** 23 | * ProductsController 24 | * 25 | * Manage CRUD operations for products 26 | */ 27 | class ProductsController extends ControllerBase 28 | { 29 | public function initialize() 30 | { 31 | parent::initialize(); 32 | 33 | $this->tag->title() 34 | ->set('Manage your products') 35 | ; 36 | } 37 | 38 | /** 39 | * Shows the index action 40 | */ 41 | public function indexAction(): void 42 | { 43 | $this->view->form = new ProductsForm(); 44 | } 45 | 46 | /** 47 | * Search products based on current criteria 48 | */ 49 | public function searchAction(): void 50 | { 51 | if ($this->request->isPost()) { 52 | $query = Criteria::fromInput( 53 | $this->di, 54 | Products::class, 55 | $this->request->getPost() 56 | ); 57 | 58 | $this->persistent->searchParams = ['di' => null] + $query->getParams(); 59 | } 60 | 61 | $parameters = []; 62 | if ($this->persistent->searchParams) { 63 | $parameters = $this->persistent->searchParams; 64 | } 65 | 66 | $products = Products::find($parameters); 67 | if (count($products) == 0) { 68 | $this->flash->notice('The search did not find any products'); 69 | 70 | $this->dispatcher->forward([ 71 | 'controller' => 'products', 72 | 'action' => 'index', 73 | ]); 74 | 75 | return; 76 | } 77 | 78 | $paginator = new Paginator([ 79 | 'model' => Products::class, 80 | 'parameters' => $parameters, 81 | 'limit' => 10, 82 | 'page' => $this->request->getQuery('page', 'int', 1), 83 | ]); 84 | 85 | $this->view->page = $paginator->paginate(); 86 | } 87 | 88 | /** 89 | * Shows the form to create a new product 90 | */ 91 | public function newAction(): void 92 | { 93 | $this->view->form = new ProductsForm(null, ['edit' => true]); 94 | } 95 | 96 | /** 97 | * Edits a product based on its id 98 | * 99 | * @param $id 100 | */ 101 | public function editAction($id): void 102 | { 103 | $product = Products::findFirstById($id); 104 | if (!$product) { 105 | $this->flash->error('Product was not found'); 106 | 107 | $this->dispatcher->forward([ 108 | 'controller' => 'products', 109 | 'action' => 'index', 110 | ]); 111 | 112 | return; 113 | } 114 | 115 | $this->view->form = new ProductsForm($product, ['edit' => true]); 116 | } 117 | 118 | /** 119 | * Creates a new product 120 | */ 121 | public function createAction(): void 122 | { 123 | if (!$this->request->isPost()) { 124 | $this->dispatcher->forward([ 125 | 'controller' => 'products', 126 | 'action' => 'index', 127 | ]); 128 | 129 | return; 130 | } 131 | 132 | $form = new ProductsForm(); 133 | $product = new Products(); 134 | $product->active = Status::ACTIVE; 135 | 136 | if (!$form->isValid($this->request->getPost(), $product)) { 137 | foreach ($form->getMessages() as $message) { 138 | $this->flash->error($message); 139 | } 140 | 141 | $this->dispatcher->forward([ 142 | 'controller' => 'products', 143 | 'action' => 'new', 144 | ]); 145 | 146 | return; 147 | } 148 | 149 | if (!$product->save()) { 150 | foreach ($product->getMessages() as $message) { 151 | $this->flash->error((string) $message); 152 | } 153 | 154 | $this->dispatcher->forward([ 155 | 'controller' => 'products', 156 | 'action' => 'new', 157 | ]); 158 | 159 | return; 160 | } 161 | 162 | $form->clear(); 163 | $this->flash->success('Product was created successfully'); 164 | 165 | $this->dispatcher->forward([ 166 | 'controller' => 'products', 167 | 'action' => 'index', 168 | ]); 169 | } 170 | 171 | /** 172 | * Saves current product in screen 173 | */ 174 | public function saveAction(): void 175 | { 176 | if (!$this->request->isPost()) { 177 | $this->dispatcher->forward([ 178 | 'controller' => 'products', 179 | 'action' => 'index', 180 | ]); 181 | 182 | return; 183 | } 184 | 185 | $id = $this->request->getPost('id', 'int'); 186 | $product = Products::findFirstById($id); 187 | if (!$product) { 188 | $this->flash->error('Product does not exist'); 189 | 190 | $this->dispatcher->forward([ 191 | 'controller' => 'products', 192 | 'action' => 'index', 193 | ]); 194 | 195 | return; 196 | } 197 | 198 | $form = new ProductsForm(); 199 | $this->view->form = $form; 200 | $data = $this->request->getPost(); 201 | 202 | if (!$form->isValid($data, $product)) { 203 | foreach ($form->getMessages() as $message) { 204 | $this->flash->error($message); 205 | } 206 | 207 | $this->dispatcher->forward([ 208 | 'controller' => 'products', 209 | 'action' => 'edit', 210 | 'params' => [$id], 211 | ]); 212 | 213 | return; 214 | } 215 | 216 | if (!$product->save()) { 217 | foreach ($product->getMessages() as $message) { 218 | $this->flash->error($message); 219 | } 220 | 221 | $this->dispatcher->forward([ 222 | 'controller' => 'products', 223 | 'action' => 'edit', 224 | 'params' => [$id], 225 | ]); 226 | 227 | return; 228 | } 229 | 230 | $form->clear(); 231 | $this->flash->success('Product was updated successfully'); 232 | 233 | $this->dispatcher->forward([ 234 | 'controller' => 'products', 235 | 'action' => 'index', 236 | ]); 237 | } 238 | 239 | /** 240 | * Deletes a product 241 | * 242 | * @param string $id 243 | */ 244 | public function deleteAction($id): void 245 | { 246 | $products = Products::findFirstById($id); 247 | if (!$products) { 248 | $this->flash->error('Product was not found'); 249 | 250 | $this->dispatcher->forward([ 251 | 'controller' => 'products', 252 | 'action' => 'index', 253 | ]); 254 | 255 | return; 256 | } 257 | 258 | if (!$products->delete()) { 259 | foreach ($products->getMessages() as $message) { 260 | $this->flash->error($message); 261 | } 262 | 263 | $this->dispatcher->forward([ 264 | 'controller' => 'products', 265 | 'action' => 'search', 266 | ]); 267 | 268 | return; 269 | } 270 | 271 | $this->flash->success('Product was deleted'); 272 | 273 | $this->dispatcher->forward([ 274 | 'controller' => 'products', 275 | 'action' => 'index', 276 | ]); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/Controllers/ProducttypesController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Invo\Forms\ProductTypesForm; 17 | use Invo\Models\ProductTypes; 18 | use Phalcon\Mvc\Model\Criteria; 19 | use Phalcon\Paginator\Adapter\Model as Paginator; 20 | 21 | /** 22 | * ProductTypesController 23 | * 24 | * Manage operations for product of types 25 | */ 26 | class ProducttypesController extends ControllerBase 27 | { 28 | public function initialize() 29 | { 30 | $this->tag->title() 31 | ->set('Manage your products types') 32 | ; 33 | 34 | parent::initialize(); 35 | } 36 | 37 | /** 38 | * Shows the index action 39 | */ 40 | public function indexAction(): void 41 | { 42 | $this->view->form = new ProductTypesForm(); 43 | } 44 | 45 | /** 46 | * Search producttype based on current criteria 47 | */ 48 | public function searchAction(): void 49 | { 50 | if ($this->request->isPost()) { 51 | $query = Criteria::fromInput( 52 | $this->di, 53 | ProductTypes::class, 54 | $this->request->getPost() 55 | ); 56 | 57 | $this->persistent->searchParams = ['di' => null] + $query->getParams(); 58 | } 59 | 60 | $parameters = []; 61 | if ($this->persistent->searchParams) { 62 | $parameters = $this->persistent->searchParams; 63 | } 64 | 65 | $productTypes = ProductTypes::find($parameters); 66 | if (count($productTypes) === 0) { 67 | $this->flash->notice('The search did not find any product types'); 68 | 69 | $this->dispatcher->forward([ 70 | 'controller' => 'producttypes', 71 | 'action' => 'index', 72 | ]); 73 | 74 | return; 75 | } 76 | 77 | $paginator = new Paginator([ 78 | 'model' => ProductTypes::class, 79 | 'parameters' => $parameters, 80 | 'limit' => 10, 81 | 'page' => $this->request->getQuery('page', 'int', 1), 82 | ]); 83 | 84 | $this->view->page = $paginator->paginate(); 85 | $this->view->productTypes = $productTypes; 86 | } 87 | 88 | /** 89 | * Shows the form to create a new producttype 90 | */ 91 | public function newAction(): void 92 | { 93 | $this->view->form = new ProductTypesForm(null, ['edit' => true]); 94 | } 95 | 96 | /** 97 | * Edits a producttype based on its id 98 | * 99 | * @param int $id 100 | */ 101 | public function editAction($id): void 102 | { 103 | $productTypes = ProductTypes::findFirstById($id); 104 | if (!$productTypes) { 105 | $this->flash->error('Product type to edit was not found'); 106 | 107 | $this->dispatcher->forward([ 108 | 'controller' => 'producttypes', 109 | 'action' => 'index', 110 | ]); 111 | 112 | return; 113 | } 114 | 115 | $this->view->form = new ProductTypesForm($productTypes, ['edit' => true]); 116 | } 117 | 118 | /** 119 | * Creates a new producttype 120 | */ 121 | public function createAction(): void 122 | { 123 | if (!$this->request->isPost()) { 124 | $this->dispatcher->forward([ 125 | 'controller' => 'producttypes', 126 | 'action' => 'index', 127 | ]); 128 | 129 | return; 130 | } 131 | 132 | $form = new ProductTypesForm(); 133 | $productTypes = new ProductTypes(); 134 | 135 | $data = $this->request->getPost(); 136 | if (!$form->isValid($data, $productTypes)) { 137 | foreach ($form->getMessages() as $message) { 138 | $this->flash->error((string) $message); 139 | } 140 | 141 | $this->dispatcher->forward([ 142 | 'controller' => 'producttypes', 143 | 'action' => 'new', 144 | ]); 145 | 146 | return; 147 | } 148 | 149 | if (!$productTypes->save()) { 150 | foreach ($productTypes->getMessages() as $message) { 151 | $this->flash->error((string) $message); 152 | } 153 | 154 | $this->dispatcher->forward([ 155 | 'controller' => 'producttypes', 156 | 'action' => 'new', 157 | ]); 158 | 159 | return; 160 | } 161 | 162 | $form->clear(); 163 | $this->flash->success('Product type was created successfully'); 164 | 165 | $this->dispatcher->forward([ 166 | 'controller' => 'producttypes', 167 | 'action' => 'index', 168 | ]); 169 | } 170 | 171 | /** 172 | * Saves current producttypes in screen 173 | */ 174 | public function saveAction(): void 175 | { 176 | if (!$this->request->isPost()) { 177 | $this->dispatcher->forward([ 178 | 'controller' => 'producttypes', 179 | 'action' => 'index', 180 | ]); 181 | 182 | return; 183 | } 184 | 185 | $id = $this->request->getPost('id', 'int'); 186 | $productTypes = ProductTypes::findFirstById($id); 187 | if (!$productTypes) { 188 | $this->flash->error('productTypes does not exist'); 189 | 190 | $this->dispatcher->forward([ 191 | 'controller' => 'producttypes', 192 | 'action' => 'index', 193 | ]); 194 | 195 | return; 196 | } 197 | 198 | $form = new ProductTypesForm(); 199 | if (!$form->isValid($this->request->getPost(), $productTypes)) { 200 | foreach ($form->getMessages() as $message) { 201 | $this->flash->error($message); 202 | } 203 | 204 | $this->dispatcher->forward([ 205 | 'controller' => 'producttypes', 206 | 'action' => 'new', 207 | ]); 208 | 209 | return; 210 | } 211 | 212 | if (!$productTypes->save()) { 213 | foreach ($productTypes->getMessages() as $message) { 214 | $this->flash->error($message); 215 | } 216 | 217 | $this->dispatcher->forward([ 218 | 'controller' => 'producttypes', 219 | 'action' => 'new', 220 | ]); 221 | 222 | return; 223 | } 224 | 225 | $form->clear(); 226 | $this->flash->success('Product Type was updated successfully'); 227 | 228 | $this->dispatcher->forward([ 229 | 'controller' => 'producttypes', 230 | 'action' => 'index', 231 | ]); 232 | } 233 | 234 | /** 235 | * Deletes a producttypes 236 | * 237 | * @param int $id 238 | */ 239 | public function deleteAction($id): void 240 | { 241 | $productTypes = ProductTypes::findFirstById($id); 242 | if (!$productTypes) { 243 | $this->flash->error('Product types was not found'); 244 | 245 | $this->dispatcher->forward([ 246 | 'controller' => 'producttypes', 247 | 'action' => 'index', 248 | ]); 249 | 250 | return; 251 | } 252 | 253 | if (!$productTypes->delete()) { 254 | foreach ($productTypes->getMessages() as $message) { 255 | $this->flash->error((string) $message); 256 | } 257 | 258 | $this->dispatcher->forward([ 259 | 'controller' => 'producttypes', 260 | 'action' => 'search', 261 | ]); 262 | 263 | return; 264 | } 265 | 266 | $this->flash->success('Product types was deleted'); 267 | 268 | $this->dispatcher->forward([ 269 | 'controller' => 'producttypes', 270 | 'action' => 'index', 271 | ]); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/Controllers/RegisterController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Invo\Constants\Status; 17 | use Invo\Forms\RegisterForm; 18 | use Invo\Models\Users; 19 | use Phalcon\Db\RawValue; 20 | 21 | /** 22 | * SessionController 23 | * 24 | * Allows to register new users 25 | */ 26 | class RegisterController extends ControllerBase 27 | { 28 | public function initialize() 29 | { 30 | $this->tag->title() 31 | ->set('Sign Up/Sign In') 32 | ; 33 | 34 | parent::initialize(); 35 | } 36 | 37 | /** 38 | * Action to register a new user 39 | */ 40 | public function indexAction(): void 41 | { 42 | $form = new RegisterForm(); 43 | 44 | if ($this->request->isPost()) { 45 | $newUser = new Users(); 46 | if ($form->isValid($this->request->getPost(), $newUser)) { 47 | $newUser->password = sha1($form->getFilteredValue('password')); 48 | $newUser->created_at = new RawValue('now()'); 49 | $newUser->active = Status::ACTIVE; 50 | 51 | if ($newUser->save()) { 52 | $this->flash->success( 53 | 'Thanks for sign-up, please log-in to start generating invoices' 54 | ); 55 | 56 | $this->dispatcher->forward([ 57 | 'controller' => 'session', 58 | 'action' => 'index', 59 | ]); 60 | 61 | return; 62 | } 63 | } else { 64 | foreach ($form->getMessages() as $message) { 65 | $this->flash->error((string) $message); 66 | } 67 | } 68 | } 69 | 70 | $this->view->form = $form; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Controllers/SessionController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Controllers; 15 | 16 | use Invo\Constants\Status; 17 | use Invo\Forms\LoginForm; 18 | use Invo\Models\Users; 19 | 20 | /** 21 | * SessionController 22 | * 23 | * Allows to authenticate users 24 | */ 25 | class SessionController extends ControllerBase 26 | { 27 | public function initialize() 28 | { 29 | parent::initialize(); 30 | 31 | $this->tag->title() 32 | ->set('Sign Up/Sign In') 33 | ; 34 | } 35 | 36 | public function indexAction(): void 37 | { 38 | $form = new LoginForm(); 39 | 40 | // Set default Invo user credentials 41 | $form->get('email')->setDefault('demo'); 42 | $form->get('password')->setDefault('phalcon'); 43 | 44 | $this->view->form = $form; 45 | } 46 | 47 | /** 48 | * This action authenticate and logs an user into the application 49 | */ 50 | public function startAction(): void 51 | { 52 | if ($this->request->isPost()) { 53 | $email = $this->request->getPost('email'); 54 | $password = $this->request->getPost('password'); 55 | 56 | /** @var Users $user */ 57 | $user = Users::findFirst( 58 | [ 59 | "conditions" => "(email = :email: OR username = :email:) " 60 | . "AND password = :password: " 61 | . "AND active = :active:", 62 | 'bind' => [ 63 | 'email' => $email, 64 | 'password' => sha1($password), 65 | 'active' => Status::ACTIVE, 66 | ], 67 | ] 68 | ); 69 | 70 | if ($user) { 71 | $this->registerSession($user); 72 | $this->flash->success('Welcome ' . $user->name); 73 | 74 | $this->dispatcher->forward([ 75 | 'controller' => 'invoices', 76 | 'action' => 'index', 77 | ]); 78 | 79 | return; 80 | } 81 | 82 | $this->flash->error('Wrong email/password'); 83 | } 84 | 85 | $this->dispatcher->forward([ 86 | 'controller' => 'session', 87 | 'action' => 'index', 88 | ]); 89 | } 90 | 91 | /** 92 | * Finishes the active session redirecting to the index 93 | */ 94 | public function endAction(): void 95 | { 96 | $this->session->remove('auth'); 97 | $this->flash->success('Goodbye!'); 98 | 99 | $this->dispatcher->forward([ 100 | 'controller' => 'index', 101 | 'action' => 'index', 102 | ]); 103 | } 104 | 105 | /** 106 | * Register an authenticated user into session data 107 | * 108 | * @param Users $user 109 | */ 110 | private function registerSession(Users $user): void 111 | { 112 | $this->session->set('auth', [ 113 | 'id' => $user->id, 114 | 'name' => $user->name, 115 | ]); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Forms/CompaniesForm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Forms; 15 | 16 | use Phalcon\Filter\Validation\Validator\PresenceOf; 17 | use Phalcon\Forms\Element\Hidden; 18 | use Phalcon\Forms\Element\Text; 19 | use Phalcon\Forms\Form; 20 | 21 | class CompaniesForm extends Form 22 | { 23 | /** 24 | * Initialize the companies form 25 | * 26 | * @param null $entity 27 | * @param array $options 28 | */ 29 | public function initialize($entity = null, array $options = []) 30 | { 31 | if (!isset($options['edit'])) { 32 | $this->add((new Text('id'))->setLabel('Id')); 33 | } else { 34 | $this->add(new Hidden('id')); 35 | } 36 | 37 | $commonFilters = [ 38 | 'striptags', 39 | 'string', 40 | ]; 41 | 42 | /** 43 | * Name text field 44 | */ 45 | $name = new Text('name'); 46 | $name->setLabel('Name'); 47 | $name->setFilters($commonFilters); 48 | $name->addValidators([ 49 | new PresenceOf(['message' => 'Name is required']), 50 | ]); 51 | 52 | $this->add($name); 53 | 54 | /** 55 | * Telephone text field 56 | */ 57 | $telephone = new Text('telephone'); 58 | $telephone->setLabel('Telephone'); 59 | $telephone->setFilters($commonFilters); 60 | $telephone->addValidators([ 61 | new PresenceOf(['message' => 'Telephone is required']), 62 | ]); 63 | 64 | $this->add($telephone); 65 | 66 | /** 67 | * Address text field 68 | */ 69 | $address = new Text('address'); 70 | $address->setLabel('address'); 71 | $address->setFilters($commonFilters); 72 | $address->addValidators([ 73 | new PresenceOf(['message' => 'Address is required']), 74 | ]); 75 | 76 | $this->add($address); 77 | 78 | /** 79 | * City text field 80 | */ 81 | $city = new Text('city'); 82 | $city->setLabel('city'); 83 | $city->setFilters($commonFilters); 84 | $city->addValidators([ 85 | new PresenceOf(['message' => 'City is required']), 86 | ]); 87 | 88 | $this->add($city); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Forms/ContactForm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Forms; 15 | 16 | use Phalcon\Filter\Validation\Validator\Email; 17 | use Phalcon\Filter\Validation\Validator\PresenceOf; 18 | use Phalcon\Forms\Element\Text; 19 | use Phalcon\Forms\Element\TextArea; 20 | use Phalcon\Forms\Form; 21 | 22 | class ContactForm extends Form 23 | { 24 | /** 25 | * @param null $entity 26 | * @param array $options 27 | */ 28 | public function initialize($entity = null, array $options = []) 29 | { 30 | /** 31 | * Name text field 32 | */ 33 | $name = new Text('name'); 34 | $name->setLabel('Your Full Name'); 35 | $name->setFilters(['striptags', 'string']); 36 | $name->addValidators([ 37 | new PresenceOf(['message' => 'Name is required']), 38 | ]); 39 | 40 | $this->add($name); 41 | 42 | /** 43 | * Email field 44 | */ 45 | $email = new Text('email'); 46 | $email->setLabel('E-Mail'); 47 | $email->setFilters('email'); 48 | $email->addValidators([ 49 | new PresenceOf(['message' => 'E-mail is required']), 50 | new Email(['message' => 'E-mail is not valid']) 51 | ]); 52 | 53 | $this->add($email); 54 | 55 | /** 56 | * Comment textarea 57 | */ 58 | $comments = new TextArea('comments'); 59 | $comments->setLabel('Comments'); 60 | $comments->setFilters(['striptags', 'string']); 61 | $comments->addValidators([ 62 | new PresenceOf(['message' => 'Comments is required']), 63 | ]); 64 | 65 | $this->add($comments); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Forms/LoginForm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Forms; 15 | 16 | use Phalcon\Filter\Validation\Validator\PresenceOf; 17 | use Phalcon\Forms\Element\Password; 18 | use Phalcon\Forms\Element\Text; 19 | use Phalcon\Forms\Form; 20 | 21 | class LoginForm extends Form 22 | { 23 | /** 24 | */ 25 | public function initialize() 26 | { 27 | /** 28 | * Username/Email text field 29 | */ 30 | $email = new Text('email'); 31 | $email->setLabel('Username/Email'); 32 | $email->setFilters(['striptags', 'string']); 33 | $email->addValidators([ 34 | new PresenceOf(['message' => 'Username/Email is required']), 35 | ]); 36 | 37 | $this->add($email); 38 | 39 | /** 40 | * Password field 41 | */ 42 | $password = new Password('password'); 43 | $password->setLabel('Password'); 44 | $password->addValidators([ 45 | new PresenceOf(['message' => 'Password is required']), 46 | ]); 47 | 48 | $this->add($password); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Forms/ProductTypesForm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Forms; 15 | 16 | use Phalcon\Filter\Validation\Validator\PresenceOf; 17 | use Phalcon\Forms\Element\Hidden; 18 | use Phalcon\Forms\Element\Text; 19 | use Phalcon\Forms\Form; 20 | 21 | class ProductTypesForm extends Form 22 | { 23 | /** 24 | * Initialize the products form 25 | * 26 | * @param null $entity 27 | * @param array $options 28 | */ 29 | public function initialize($entity = null, array $options = []) 30 | { 31 | if (!isset($options['edit'])) { 32 | $this->add((new Text('id'))->setLabel('Id')); 33 | } else { 34 | $this->add(new Hidden('id')); 35 | } 36 | 37 | $name = new Text('name'); 38 | $name->setLabel('Name'); 39 | $name->setFilters(['striptags', 'string']); 40 | $name->addValidators([ 41 | new PresenceOf(['message' => 'Name is required']), 42 | ]); 43 | 44 | $this->add($name); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Forms/ProductsForm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Forms; 15 | 16 | use Invo\Models\ProductTypes; 17 | use Phalcon\Filter\Validation\Validator\Numericality; 18 | use Phalcon\Filter\Validation\Validator\PresenceOf; 19 | use Phalcon\Forms\Element\Hidden; 20 | use Phalcon\Forms\Element\Select; 21 | use Phalcon\Forms\Element\Text; 22 | use Phalcon\Forms\Form; 23 | 24 | class ProductsForm extends Form 25 | { 26 | /** 27 | * Initialize the products form 28 | * 29 | * @param null $entity 30 | * @param array $options 31 | */ 32 | public function initialize($entity = null, array $options = []) 33 | { 34 | if (!isset($options['edit'])) { 35 | $this->add((new Text('id'))->setLabel('Id')); 36 | } else { 37 | $this->add(new Hidden('id')); 38 | } 39 | 40 | /** 41 | * Name text field 42 | */ 43 | $name = new Text('name'); 44 | $name->setLabel('Name'); 45 | $name->setFilters(['striptags', 'string']); 46 | $name->addValidators([ 47 | new PresenceOf(['message' => 'Name is required']), 48 | ]); 49 | 50 | $this->add($name); 51 | 52 | /** 53 | * Product Type Id Select 54 | */ 55 | $type = new Select( 56 | 'product_types_id', 57 | ProductTypes::find(), 58 | [ 59 | 'using' => ['id', 'name'], 60 | 'useEmpty' => true, 61 | 'emptyText' => '...', 62 | 'emptyValue' => '', 63 | ] 64 | ); 65 | $type->setLabel('Type'); 66 | 67 | $this->add($type); 68 | 69 | /** 70 | * Price text field 71 | */ 72 | $price = new Text('price'); 73 | $price->setLabel('Price'); 74 | $price->setFilters(['float']); 75 | $price->addValidators([ 76 | new PresenceOf(['message' => 'Price is required']), 77 | new Numericality(['message' => 'Price is required']), 78 | ]); 79 | 80 | $this->add($price); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Forms/ProfileForm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Forms; 15 | 16 | use Phalcon\Filter\Validation\Validator\Email; 17 | use Phalcon\Filter\Validation\Validator\PresenceOf; 18 | use Phalcon\Filter\Validation\Validator\Uniqueness as UniquenessValidator; 19 | use Phalcon\Forms\Element\Text; 20 | use Phalcon\Forms\Form; 21 | 22 | class ProfileForm extends Form 23 | { 24 | public function initialize() 25 | { 26 | /** 27 | * Name text field 28 | */ 29 | $name = new Text('name'); 30 | $name->setLabel('Your Full Name'); 31 | $name->setFilters(['striptags', 'string']); 32 | $name->addValidators([ 33 | new PresenceOf(['message' => 'Name is required']), 34 | ]); 35 | 36 | $this->add($name); 37 | 38 | /** 39 | * Email text field 40 | */ 41 | $email = new Text('email'); 42 | $email->setLabel('E-Mail Address'); 43 | $email->setFilters('email'); 44 | $email->addValidators([ 45 | new PresenceOf(['message' => 'E-mail is required']), 46 | new Email(['message' => 'E-mail is not valid']), 47 | new UniquenessValidator( 48 | [ 49 | 'message' => 'Sorry, The email was registered by another user', 50 | ] 51 | ) 52 | ]); 53 | 54 | $this->add($email); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Forms/RegisterForm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Forms; 15 | 16 | use Phalcon\Filter\Validation\Validator\Email; 17 | use Phalcon\Filter\Validation\Validator\PresenceOf; 18 | use Phalcon\Filter\Validation\Validator\Uniqueness as UniquenessValidator; 19 | use Phalcon\Filter\Validation\Validator\Confirmation; 20 | use Phalcon\Filter\Validation\Validator\StringLength\Min; 21 | use Phalcon\Forms\Element\Password; 22 | use Phalcon\Forms\Element\Text; 23 | use Phalcon\Forms\Form; 24 | 25 | class RegisterForm extends Form 26 | { 27 | /** 28 | * @param null $entity 29 | * @param null $options 30 | */ 31 | public function initialize($entity = null, $options = null) 32 | { 33 | /** 34 | * Name text field 35 | */ 36 | $name = new Text('name'); 37 | $name->setLabel('Your Full Name'); 38 | $name->setFilters(['striptags', 'string']); 39 | $name->addValidators([ 40 | new PresenceOf(['message' => 'Name is required']), 41 | ]); 42 | 43 | $this->add($name); 44 | 45 | /** 46 | * Username text field 47 | */ 48 | $name = new Text('username'); 49 | $name->setLabel('Username'); 50 | $name->setFilters(['alnum']); 51 | $name->addValidators([ 52 | new PresenceOf(['message' => 'Please enter your desired user name']), 53 | new UniquenessValidator( 54 | [ 55 | 'message' => 'Sorry, That username is already taken', 56 | ] 57 | ) 58 | ]); 59 | 60 | $this->add($name); 61 | 62 | /** 63 | * Email text field 64 | */ 65 | $email = new Text('email'); 66 | $email->setLabel('E-Mail'); 67 | $email->setFilters('email'); 68 | $email->addValidators([ 69 | new PresenceOf(['message' => 'E-mail is required']), 70 | new Email(['message' => 'E-mail is not valid']), 71 | new UniquenessValidator( 72 | [ 73 | 'message' => 'Sorry, The email was registered by another user', 74 | ] 75 | ) 76 | ]); 77 | 78 | $this->add($email); 79 | 80 | /** 81 | * Password field 82 | */ 83 | $password = new Password('password'); 84 | $password->setLabel('Password'); 85 | $password->addValidators([ 86 | new PresenceOf(['message' => 'Password is required']), 87 | new Min(['min' => 8, 'message' => 'Password must be at least 8 characters']), 88 | ]); 89 | 90 | $this->add($password); 91 | 92 | /** 93 | * Confirm Password field 94 | */ 95 | $repeatPassword = new Password('repeatPassword'); 96 | $repeatPassword->setLabel('Repeat Password'); 97 | $repeatPassword->addValidators([ 98 | new PresenceOf(['message' => 'Confirmation password is required']), 99 | new Confirmation(["message" => "Passwords are different", "with" => "password",]), 100 | ]); 101 | 102 | $this->add($repeatPassword); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Models/Companies.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Models; 15 | 16 | use Phalcon\Mvc\Model; 17 | 18 | class Companies extends Model 19 | { 20 | /** 21 | * @var integer 22 | */ 23 | public $id; 24 | 25 | /** 26 | * @var string 27 | */ 28 | public $name; 29 | 30 | /** 31 | * @var string 32 | */ 33 | public $telephone; 34 | 35 | /** 36 | * @var string 37 | */ 38 | public $address; 39 | 40 | /** 41 | * @var string 42 | */ 43 | public $city; 44 | } 45 | -------------------------------------------------------------------------------- /src/Models/Contact.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Models; 15 | 16 | use Phalcon\Db\RawValue; 17 | use Phalcon\Mvc\Model; 18 | 19 | class Contact extends Model 20 | { 21 | public $id; 22 | 23 | public $name; 24 | 25 | public $email; 26 | 27 | public $comments; 28 | 29 | public $created_at; 30 | 31 | public function beforeCreate() 32 | { 33 | $this->created_at = new RawValue('now()'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Models/ProductTypes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Models; 15 | 16 | use Phalcon\Mvc\Model; 17 | 18 | /** 19 | * Types of Products 20 | */ 21 | class ProductTypes extends Model 22 | { 23 | /** 24 | * @var integer 25 | */ 26 | public $id; 27 | 28 | /** 29 | * @var string 30 | */ 31 | public $name; 32 | 33 | /** 34 | * ProductTypes initializer 35 | */ 36 | public function initialize() 37 | { 38 | $this->hasMany( 39 | 'id', 40 | Products::class, 41 | 'product_types_id', 42 | [ 43 | 'foreignKey' => [ 44 | 'message' => 'Product Type cannot be deleted because it\'s used in Products' 45 | ], 46 | ] 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Models/Products.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Models; 15 | 16 | use Invo\Constants\Status; 17 | use Phalcon\Mvc\Model; 18 | 19 | /** 20 | * Products 21 | * @property ProductTypes $productType 22 | */ 23 | class Products extends Model 24 | { 25 | /** 26 | * @var integer 27 | */ 28 | public $id; 29 | 30 | /** 31 | * @var integer 32 | */ 33 | public $product_types_id; 34 | 35 | /** 36 | * @var string 37 | */ 38 | public $name; 39 | 40 | /** 41 | * @var string 42 | */ 43 | public $price; 44 | 45 | /** 46 | * @var string 47 | */ 48 | public $active; 49 | 50 | /** 51 | * Products initializer 52 | */ 53 | public function initialize() 54 | { 55 | $this->belongsTo( 56 | 'product_types_id', 57 | ProductTypes::class, 58 | 'id', 59 | [ 60 | 'reusable' => true, 61 | 'alias' => 'productTypes', 62 | ] 63 | ); 64 | } 65 | 66 | /** 67 | * Returns a human representation of 'active' 68 | * 69 | * @return string 70 | */ 71 | public function getActiveDetail(): string 72 | { 73 | return $this->active == Status::ACTIVE ? 'Yes' : 'No'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Models/Users.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Models; 15 | 16 | use Phalcon\Db\RawValue; 17 | use Phalcon\Mvc\Model; 18 | 19 | class Users extends Model 20 | { 21 | /** 22 | * @var integer|null 23 | */ 24 | public ?int $id = null; 25 | 26 | /** 27 | * @var string 28 | */ 29 | public string $username; 30 | 31 | /** 32 | * @var string 33 | */ 34 | public string $password; 35 | 36 | /** 37 | * @var string 38 | */ 39 | public string $name; 40 | 41 | /** 42 | * @var string 43 | */ 44 | public string $email; 45 | 46 | /** 47 | * @var string|RawValue 48 | */ 49 | public string|RawValue $created_at; 50 | 51 | /** 52 | * @var integer 53 | */ 54 | public int $active; 55 | } 56 | -------------------------------------------------------------------------------- /src/Plugins/NotFoundPlugin.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Plugins; 15 | 16 | use Exception; 17 | use Phalcon\Di\Injectable; 18 | use Phalcon\Events\Event; 19 | use Phalcon\Mvc\Dispatcher as MvcDispatcher; 20 | use Phalcon\Mvc\Dispatcher\Exception as DispatcherException; 21 | 22 | /** 23 | * NotFoundPlugin 24 | * 25 | * Handles not-found controller/actions 26 | */ 27 | class NotFoundPlugin extends Injectable 28 | { 29 | /** 30 | * This action is executed before perform any action in the application 31 | * 32 | * @param Event $event 33 | * @param MvcDispatcher $dispatcher 34 | * @param Exception $exception 35 | * 36 | * @return bool 37 | */ 38 | public function beforeException(Event $event, MvcDispatcher $dispatcher, Exception $exception) 39 | { 40 | error_log($exception->getMessage() . PHP_EOL . $exception->getTraceAsString()); 41 | 42 | if ($exception instanceof DispatcherException) { 43 | switch ($exception->getCode()) { 44 | case DispatcherException::EXCEPTION_HANDLER_NOT_FOUND: 45 | case DispatcherException::EXCEPTION_ACTION_NOT_FOUND: 46 | $dispatcher->forward([ 47 | 'controller' => 'errors', 48 | 'action' => 'show404', 49 | ]); 50 | 51 | return false; 52 | } 53 | } 54 | 55 | if ($dispatcher->getControllerName() !== 'errors') { 56 | $dispatcher->forward([ 57 | 'controller' => 'errors', 58 | 'action' => 'show500', 59 | ]); 60 | } 61 | 62 | return !$event->isStopped(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Plugins/SecurityPlugin.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Plugins; 15 | 16 | use Phalcon\Acl\Adapter\Memory as AclList; 17 | use Phalcon\Acl\Component; 18 | use Phalcon\Acl\Enum; 19 | use Phalcon\Acl\Role; 20 | use Phalcon\Di\Injectable; 21 | use Phalcon\Events\Event; 22 | use Phalcon\Mvc\Dispatcher; 23 | 24 | /** 25 | * SecurityPlugin 26 | * 27 | * This is the security plugin which controls that users only have access to the modules they're assigned to 28 | */ 29 | class SecurityPlugin extends Injectable 30 | { 31 | /** 32 | * This action is executed before execute any action in the application 33 | * 34 | * @param Event $event 35 | * @param Dispatcher $dispatcher 36 | * 37 | * @return bool 38 | */ 39 | public function beforeExecuteRoute(Event $event, Dispatcher $dispatcher) 40 | { 41 | $auth = $this->session->get('auth'); 42 | if (!$auth) { 43 | $role = 'Guests'; 44 | } else { 45 | $role = 'Users'; 46 | } 47 | 48 | $controller = $dispatcher->getControllerName(); 49 | $action = $dispatcher->getActionName(); 50 | 51 | $acl = $this->getAcl(); 52 | 53 | if (!$acl->isComponent($controller)) { 54 | $dispatcher->forward([ 55 | 'controller' => 'errors', 56 | 'action' => 'show404', 57 | ]); 58 | 59 | return false; 60 | } 61 | 62 | $allowed = $acl->isAllowed($role, $controller, $action); 63 | if (!$allowed) { 64 | $dispatcher->forward([ 65 | 'controller' => 'errors', 66 | 'action' => 'show401', 67 | ]); 68 | 69 | $this->session->destroy(); 70 | 71 | return false; 72 | } 73 | 74 | return true; 75 | } 76 | 77 | /** 78 | * Returns an existing or new access control list 79 | * 80 | * @returns AclList 81 | */ 82 | protected function getAcl(): AclList 83 | { 84 | if (isset($this->persistent->acl)) { 85 | return $this->persistent->acl; 86 | } 87 | 88 | $acl = new AclList(); 89 | $acl->setDefaultAction(Enum::DENY); 90 | 91 | // Register roles 92 | $roles = [ 93 | 'users' => new Role( 94 | 'Users', 95 | 'Member privileges, granted after sign in.' 96 | ), 97 | 'guests' => new Role( 98 | 'Guests', 99 | 'Anyone browsing the site who is not signed in is considered to be a "Guest".' 100 | ) 101 | ]; 102 | 103 | foreach ($roles as $role) { 104 | $acl->addRole($role); 105 | } 106 | 107 | //Private area resources 108 | $privateResources = [ 109 | 'companies' => ['index', 'search', 'new', 'edit', 'save', 'create', 'delete'], 110 | 'products' => ['index', 'search', 'new', 'edit', 'save', 'create', 'delete'], 111 | 'producttypes' => ['index', 'search', 'new', 'edit', 'save', 'create', 'delete'], 112 | 'invoices' => ['index', 'profile'], 113 | ]; 114 | foreach ($privateResources as $resource => $actions) { 115 | $acl->addComponent(new Component($resource), $actions); 116 | } 117 | 118 | //Public area resources 119 | $publicResources = [ 120 | 'index' => ['index'], 121 | 'about' => ['index'], 122 | 'register' => ['index'], 123 | 'errors' => ['show401', 'show404', 'show500'], 124 | 'session' => ['index', 'register', 'start', 'end'], 125 | 'contact' => ['index', 'send'], 126 | ]; 127 | foreach ($publicResources as $resource => $actions) { 128 | $acl->addComponent(new Component($resource), $actions); 129 | } 130 | 131 | //Grant access to public areas to both users and guests 132 | foreach ($roles as $role) { 133 | foreach ($publicResources as $resource => $actions) { 134 | foreach ($actions as $action) { 135 | $acl->allow($role->getName(), $resource, $action); 136 | } 137 | } 138 | } 139 | 140 | //Grant access to private area to role Users 141 | foreach ($privateResources as $resource => $actions) { 142 | foreach ($actions as $action) { 143 | $acl->allow('Users', $resource, $action); 144 | } 145 | } 146 | 147 | //The acl is stored in session, APC would be useful here too 148 | $this->persistent->acl = $acl; 149 | 150 | return $acl; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Providers/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Exception; 17 | use Phalcon\Di\DiInterface; 18 | use Phalcon\Di\ServiceProviderInterface; 19 | 20 | /** 21 | * Read the configuration 22 | */ 23 | class ConfigProvider implements ServiceProviderInterface 24 | { 25 | public function register(DiInterface $di): void 26 | { 27 | $configPath = $di->offsetGet('rootPath') . '/config/config.php'; 28 | if (!file_exists($configPath) || !is_readable($configPath)) { 29 | throw new Exception('Config file does not exist: ' . $configPath); 30 | } 31 | 32 | $di->setShared('config', function () use ($configPath) { 33 | return require_once $configPath; 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Providers/DatabaseProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | 19 | use function var_dump; 20 | 21 | /** 22 | * Database connection is created based in the parameters defined in the configuration file 23 | */ 24 | class DatabaseProvider implements ServiceProviderInterface 25 | { 26 | public function register(DiInterface $di): void 27 | { 28 | $dbConfig = $di->getShared('config') 29 | ->get('database') 30 | ->toArray() 31 | ; 32 | $di->setShared('db', function () use ($dbConfig) { 33 | $dbClass = 'Phalcon\Db\Adapter\Pdo\\' . $dbConfig['adapter']; 34 | unset($dbConfig['adapter']); 35 | 36 | return new $dbClass($dbConfig); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Providers/DispatcherProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Invo\Plugins\NotFoundPlugin; 17 | use Invo\Plugins\SecurityPlugin; 18 | use Phalcon\Di\DiInterface; 19 | use Phalcon\Di\ServiceProviderInterface; 20 | use Phalcon\Events\Manager as EventsManager; 21 | use Phalcon\Mvc\Dispatcher; 22 | 23 | /** 24 | * We register the events manager 25 | */ 26 | class DispatcherProvider implements ServiceProviderInterface 27 | { 28 | public function register(DiInterface $di): void 29 | { 30 | $di->setShared('dispatcher', function () { 31 | $eventsManager = new EventsManager(); 32 | 33 | /** 34 | * Check if the user is allowed to access certain action using the SecurityPlugin 35 | */ 36 | $eventsManager->attach('dispatch:beforeExecuteRoute', new SecurityPlugin()); 37 | 38 | /** 39 | * Handle exceptions and not-found exceptions using NotFoundPlugin 40 | */ 41 | $eventsManager->attach('dispatch:beforeException', new NotFoundPlugin()); 42 | 43 | $dispatcher = new Dispatcher(); 44 | $dispatcher->setDefaultNamespace('Invo\Controllers'); 45 | $dispatcher->setEventsManager($eventsManager); 46 | 47 | return $dispatcher; 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Providers/FlashProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | use Phalcon\Flash\Direct as FlashDirect; 19 | 20 | /** 21 | * Register the flash service with custom CSS classes 22 | */ 23 | class FlashProvider implements ServiceProviderInterface 24 | { 25 | public function register(DiInterface $di): void 26 | { 27 | $di->setShared('flash', function () { 28 | $flash = new FlashDirect(); 29 | $flash->setImplicitFlush(false); 30 | $flash->setCssClasses([ 31 | 'error' => 'alert alert-danger', 32 | 'success' => 'alert alert-success', 33 | 'notice' => 'alert alert-info', 34 | 'warning' => 'alert alert-warning' 35 | ]); 36 | 37 | return $flash; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Providers/SessionBagProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | use Phalcon\Session\Bag; 19 | 20 | class SessionBagProvider implements ServiceProviderInterface 21 | { 22 | public function register(DiInterface $di): void 23 | { 24 | $session = $di->getShared('session'); 25 | $di->setShared( 26 | 'sessionBag', 27 | function () use ($session) { 28 | return new Bag($session, 'bag'); 29 | } 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Providers/SessionProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | use Phalcon\Session\Adapter\Stream as SessionAdapter; 19 | use Phalcon\Session\Manager as SessionManager; 20 | 21 | /** 22 | * Start the session the first time some component request the session service 23 | */ 24 | class SessionProvider implements ServiceProviderInterface 25 | { 26 | public function register(DiInterface $di): void 27 | { 28 | $di->setShared('session', function () { 29 | $session = new SessionManager(); 30 | $files = new SessionAdapter([ 31 | 'savePath' => sys_get_temp_dir(), 32 | ]); 33 | $session->setAdapter($files); 34 | $session->start(); 35 | 36 | return $session; 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Providers/UrlProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | use Phalcon\Mvc\Url; 19 | 20 | /** 21 | * The URL component is used to generate all kind of urls in the application 22 | */ 23 | class UrlProvider implements ServiceProviderInterface 24 | { 25 | public function register(DiInterface $di): void 26 | { 27 | $baseUri = $di->getShared('config')->application->baseUri; 28 | $di->setShared('url', function () use ($baseUri) { 29 | $url = new Url(); 30 | $url->setBaseUri($baseUri); 31 | 32 | return $url; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Providers/ViewProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | use Phalcon\Mvc\View; 19 | 20 | class ViewProvider implements ServiceProviderInterface 21 | { 22 | public function register(DiInterface $di): void 23 | { 24 | $viewsDir = $di->get('rootPath') . DIRECTORY_SEPARATOR . $di->getShared('config')->application->viewsDir; 25 | 26 | $di->setShared('view', function () use ($viewsDir) { 27 | $view = new View(); 28 | $view->setViewsDir($viewsDir); 29 | $view->registerEngines([ 30 | '.volt' => 'volt' 31 | ]); 32 | 33 | return $view; 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Providers/VoltProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Invo\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | use Phalcon\Mvc\View\Engine\Volt as VoltEngine; 19 | 20 | class VoltProvider implements ServiceProviderInterface 21 | { 22 | public function register(DiInterface $di): void 23 | { 24 | $view = $di->getShared('view'); 25 | 26 | $di->setShared('volt', function () use ($view, $di) { 27 | $volt = new VoltEngine($view, $di); 28 | $volt->setOptions([ 29 | 'path' => $di->offsetGet('rootPath') . '/var/cache/volt/', 30 | ]); 31 | 32 | $compiler = $volt->getCompiler(); 33 | $compiler->addFunction('is_a', 'is_a'); 34 | 35 | return $volt; 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | amOnPage('/about'); 14 | $I->see('About INVO'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/acceptance/Controllers/ErrorsControllerCest.php: -------------------------------------------------------------------------------- 1 | amOnPage('/test'); 14 | $I->see('Page not found'); 15 | $I->seeResponseCodeIs(404); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/acceptance/Controllers/IndexControllerCest.php: -------------------------------------------------------------------------------- 1 | amOnPage('/'); 14 | $I->see('Welcome to INVO'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/functional.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | # 3 | # Suite for functional tests 4 | # Emulate web requests and make application process them 5 | # Include one of framework modules (Symfony2, Yii2, Laravel5) to use it 6 | # Remove this suite if you don't use frameworks 7 | 8 | actor: FunctionalTester 9 | modules: 10 | enabled: 11 | # add a framework module here 12 | - \Helper\Functional 13 | step_decorators: ~ -------------------------------------------------------------------------------- /tests/functional/Forms/ProductTypesFormTest.php: -------------------------------------------------------------------------------- 1 | 'string'], true], 25 | [[$key => '

Title

'], true], 26 | [[$key => 1], true], 27 | [[], false], 28 | ]; 29 | } 30 | 31 | /** 32 | * @dataProvider inputDataProvider 33 | * 34 | * @param array $data 35 | * @param bool $expected 36 | */ 37 | public function testValidation(array $data, bool $expected): void 38 | { 39 | $di = new Di(); 40 | $di['filter'] = function () { 41 | $factory = new FilterFactory(); 42 | return $factory->newInstance(); 43 | }; 44 | 45 | $form = new ProductTypesForm(); 46 | $form->setDI($di); 47 | 48 | $this->assertSame($expected, $form->isValid($data)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/functional/Models/ContactTest.php: -------------------------------------------------------------------------------- 1 | setDI($di); 29 | $class->beforeCreate(); 30 | 31 | /** @var RawValue $raw */ 32 | $raw = $class->created_at; 33 | 34 | $this->assertSame('now()', $raw->__toString()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/unit.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | # 3 | # Suite for unit or integration tests. 4 | 5 | actor: UnitTester 6 | modules: 7 | enabled: 8 | - Asserts 9 | - \Helper\Unit 10 | step_decorators: ~ -------------------------------------------------------------------------------- /tests/unit/Controllers/AboutControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(AboutController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/CompaniesControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(CompaniesController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/ContactControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContactController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/ControllerBaseTest.php: -------------------------------------------------------------------------------- 1 | createMock(ControllerBase::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/ErrorsControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(ErrorsController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/IndexControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(IndexController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/InvoicesControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(InvoicesController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/ProductTypesControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(ProducttypesController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/ProductsControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(ProductsController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/RegisterControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(RegisterController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Controllers/SessionControllerTest.php: -------------------------------------------------------------------------------- 1 | createMock(SessionController::class); 16 | 17 | $this->assertInstanceOf(Controller::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Forms/CompaniesFormTest.php: -------------------------------------------------------------------------------- 1 | createMock(CompaniesForm::class); 18 | 19 | $this->assertInstanceOf(Form::class, $class); 20 | } 21 | 22 | public function testIdElementType(): void 23 | { 24 | $createForm = new CompaniesForm(); 25 | $editForm = new CompaniesForm(null, ['edit' => true]); 26 | 27 | $this->assertInstanceOf(Text::class, $createForm->getElements()['id']); 28 | $this->assertInstanceOf(Hidden::class, $editForm->getElements()['id']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/Forms/ContactFormTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContactForm::class); 16 | 17 | $this->assertInstanceOf(Form::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Forms/LoginFormTest.php: -------------------------------------------------------------------------------- 1 | createMock(LoginForm::class); 16 | 17 | $this->assertInstanceOf(Form::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Forms/ProductTypesFormTest.php: -------------------------------------------------------------------------------- 1 | createMock(ProductTypesForm::class); 16 | 17 | $this->assertInstanceOf(Form::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Forms/ProductsFormTest.php: -------------------------------------------------------------------------------- 1 | createMock(ProductsForm::class); 16 | 17 | $this->assertInstanceOf(Form::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Forms/RegisterFormTest.php: -------------------------------------------------------------------------------- 1 | createMock(RegisterForm::class); 16 | 17 | $this->assertInstanceOf(Form::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Models/CompaniesTest.php: -------------------------------------------------------------------------------- 1 | createMock(Companies::class); 16 | 17 | $this->assertInstanceOf(Model::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Models/ContactTest.php: -------------------------------------------------------------------------------- 1 | createMock(Contact::class); 16 | 17 | $this->assertInstanceOf(Model::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Models/ProductTypesTest.php: -------------------------------------------------------------------------------- 1 | createMock(ProductTypes::class); 16 | 17 | $this->assertInstanceOf(Model::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Models/ProductsTest.php: -------------------------------------------------------------------------------- 1 | createMock(Products::class); 16 | 17 | $this->assertInstanceOf(Model::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Models/UsersTest.php: -------------------------------------------------------------------------------- 1 | createMock(Users::class); 16 | 17 | $this->assertInstanceOf(Model::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Plugins/NotFoundPluginTest.php: -------------------------------------------------------------------------------- 1 | createMock(NotFoundPlugin::class); 16 | 17 | $this->assertInstanceOf(Injectable::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Plugins/SecurityPluginTest.php: -------------------------------------------------------------------------------- 1 | createMock(SecurityPlugin::class); 16 | 17 | $this->assertInstanceOf(Injectable::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/ConfigProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(ConfigProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/DatabaseProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(DatabaseProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/DispatcherProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(DispatcherProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/FlashProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(FlashProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/SessionBagProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(SessionBagProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/SessionProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(SessionProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/UrlProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(UrlProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/ViewProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(ViewProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/Providers/VoltProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(VoltProvider::class); 16 | 17 | $this->assertInstanceOf(ServiceProviderInterface::class, $class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /themes/invo/about/index.volt: -------------------------------------------------------------------------------- 1 | 4 | 5 |

6 | This is a sample application for the Phalcon Framework. 7 | We expect to implement as many features as possible to show how the framework works and its potential. 8 | Please write us if you have any feedback or comments. 9 | Feel free to clone the code of this application here. 10 | Thanks! 11 |

12 | -------------------------------------------------------------------------------- /themes/invo/companies/edit.volt: -------------------------------------------------------------------------------- 1 |
2 |
    3 | 6 |
  • 7 | {{ tag.inputSubmit('Save', 'Save', ['class': 'btn btn-success']) }} 8 |
  • 9 |
10 | 11 |

Edit companies

12 | 13 |
14 | {% for element in form %} 15 | {% if is_a(element, 'Phalcon\Forms\Element\Hidden') %} 16 | {{ element }} 17 | {% else %} 18 |
19 | {{ element.label(['class': 'control-label']) }} 20 |
21 | {{ element }} 22 |
23 |
24 | {% endif %} 25 | {% endfor %} 26 |
27 |
28 | -------------------------------------------------------------------------------- /themes/invo/companies/index.volt: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Search companies

4 |
5 |
6 | {{ tag.a(url('companies/new'), 'Create Companies', ['class':'btn btn-primary']) }} 7 |
8 |
9 | 10 |
11 | {% for element in form %} 12 | {% if is_a(element, 'Phalcon\Forms\Element\Hidden') %} 13 | {{ element }} 14 | {% else %} 15 | 16 |
17 | {{ element.label() }} 18 |
19 | {{ element.setAttribute("class", "form-control") }} 20 |
21 |
22 | {% endif %} 23 | {% endfor %} 24 | 25 | {{ tag.inputSubmit('Search', null, ['class': 'btn btn-primary', 'id': null, 'name': null, 'value': 'Search']) }} 26 |
27 | -------------------------------------------------------------------------------- /themes/invo/companies/new.volt: -------------------------------------------------------------------------------- 1 |
2 |
    3 | 6 |
  • 7 | {{ tag.inputSubmit('Save', 'Save', ['class': 'btn btn-success', 'id':null, 'name':null]) }} 8 |
  • 9 |
10 | 11 |
12 | {% for element in form %} 13 | {% if is_a(element, 'Phalcon\Forms\Element\Hidden') %} 14 | {{ element }} 15 | {% else %} 16 |
17 | {{ element.label() }} 18 | {{ element.render(['class': 'form-control']) }} 19 |
20 | {% endif %} 21 | {% endfor %} 22 |
23 |
24 | -------------------------------------------------------------------------------- /themes/invo/companies/search.volt: -------------------------------------------------------------------------------- 1 | 9 | 10 | {% for company in page.items %} 11 | {% if loop.first %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% endif %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% if loop.last %} 34 | 35 | 44 | 45 | 46 |
IdNameTelephoneAddressCity
{{ company.id }}{{ company.name }}{{ company.telephone }}{{ company.address }}{{ company.city }}{{ tag.a(url('companies/edit/' ~ company.id), " Edit", ['class': 'btn btn-default'], true) }}{{ tag.a(url('companies/delete/' ~ company.id), " Delete", ['class': 'btn btn-default'], true) }}
36 |
37 | {{ tag.a(url('companies/search'), " First", ['class': 'btn btn-default'], true) }} 38 | {{ tag.a(url('companies/search?page=') ~ page.getPrevious(), " Previous", ['class': 'btn btn-default'], true) }} 39 | {{ tag.a(url('companies/search?page=') ~ page.getNext(), " Next", ['class': 'btn btn-default'], true) }} 40 | {{ tag.a(url('companies/search?page=') ~ page.getLast(), " Last", ['class': 'btn btn-default'], true) }} 41 | {{ page.getCurrent() }}/{{ page.getLast() }} 42 |
43 |
47 | {% endif %} 48 | {% else %} 49 | No companies were found... 50 | {% endfor %} 51 | -------------------------------------------------------------------------------- /themes/invo/contact/index.volt: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Send us a message and let us know how we can help. Please be as descriptive as possible as it will help us serve you better.

6 | 7 |
8 |
9 |
10 | {{ form.label('name') }} 11 | {{ form.render('name', ['class': 'form-control']) }} 12 |
13 |
14 | {{ form.label('email') }} 15 | {{ form.render('email', ['class': 'form-control']) }} 16 |
17 |
18 | {{ form.label('comments') }} 19 | {{ form.render('comments', ['class': 'form-control']) }} 20 |
21 |
22 | {{ tag.inputSubmit('Save', 'Save', ['class': 'btn btn-primary btn-large']) }} 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /themes/invo/errors/show401.volt: -------------------------------------------------------------------------------- 1 |
2 |

Unauthorized

3 |

You don't have access to this option. Contact an administrator

4 |

{{ tag.a(url('index'), 'Home', ['class':'btn btn-primary']) }}

5 |
6 | -------------------------------------------------------------------------------- /themes/invo/errors/show404.volt: -------------------------------------------------------------------------------- 1 |
2 |

Page not found

3 |

Sorry, you have accessed a page that does not exist or was moved

4 |

{{ tag.a(url('index'), 'Home', ['class':'btn btn-primary']) }}

5 |
6 | -------------------------------------------------------------------------------- /themes/invo/errors/show500.volt: -------------------------------------------------------------------------------- 1 |
2 |

Internal Error

3 |

Something went wrong, if the error continue please contact us

4 |

{{ tag.a(url('index'), 'Home', ['class':'btn btn-primary']) }}

5 |
6 | -------------------------------------------------------------------------------- /themes/invo/index.volt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title('') }} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{ content() }} 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{ tag.script('').add(url('js/utils.js'), ['type':null]) }} 22 | 23 | 24 | -------------------------------------------------------------------------------- /themes/invo/index/index.volt: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to INVO

3 |

INVO is a revolutionary application to create invoices online for free. 4 | Receive online payments from your clients and improve your cash flow

5 |

{{ tag.a(url('register'), 'Try it for Free »', ['class':'btn btn-primary btn-large btn-success'], true) }}

6 |
7 | 8 |
9 |
10 |

Manage Invoices Online

11 |

Create, track and export your invoices online. Automate recurring invoices and design your own invoice using our invoice template and brand it with your business logo.

12 |
13 |
14 |

Dashboards And Reports

15 |

Gain critical insights into how your business is doing. See what sells most, who are your top paying customers and the average time your customers take to pay.

16 |
17 |
18 |

Invite, Share And Collaborate

19 |

Invite users and share your workload as invoice supports multiple users with different permissions. It helps your business to be more productive and efficient.

20 |
21 |
22 | -------------------------------------------------------------------------------- /themes/invo/invoices/index.volt: -------------------------------------------------------------------------------- 1 |

Your Invoices

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 |
NumberCustomerDateTotalStatus
51001Friðrik Þór Friðriksson2014-04-0212.50Success
51002Keith Carradine2014-04-0422.75Rejected
51003Nico Engelbrecht2014-04-046.50Success
51004Clinton Kayser2014-04-0711.50Success
44 | -------------------------------------------------------------------------------- /themes/invo/invoices/profile.volt: -------------------------------------------------------------------------------- 1 |

Update your profile

2 | 3 |
4 |
5 | {{ form.label('name', ['class': 'control-label']) }} 6 | {{ form.render('name', ['class': 'form-control']) }} 7 |
8 | Warning! Please enter your full name 9 |
10 |
11 | 12 |
13 | {{ form.label('email', ['class': 'control-label']) }} 14 | {{ form.render('email', ['class': 'form-control']) }} 15 |
16 | Warning! Please enter your email 17 |
18 |
19 | 20 | 21 | {{ tag.a(url('invoices/index'), 'Cancel', ['class':'btn btn-default']) }} 22 |
23 | -------------------------------------------------------------------------------- /themes/invo/layouts/about.volt: -------------------------------------------------------------------------------- 1 | 2 | {{ content() }} 3 | -------------------------------------------------------------------------------- /themes/invo/layouts/companies.volt: -------------------------------------------------------------------------------- 1 | {{ partial('layouts/patial_tabs') }} 2 | 3 | 4 | {{ content() }} 5 | -------------------------------------------------------------------------------- /themes/invo/layouts/contact.volt: -------------------------------------------------------------------------------- 1 | 2 | {{ content() }} 3 | -------------------------------------------------------------------------------- /themes/invo/layouts/errors.volt: -------------------------------------------------------------------------------- 1 | 2 | {{ content() }} 3 | -------------------------------------------------------------------------------- /themes/invo/layouts/index.volt: -------------------------------------------------------------------------------- 1 | 2 | {{ content() }} 3 | -------------------------------------------------------------------------------- /themes/invo/layouts/invoices.volt: -------------------------------------------------------------------------------- 1 | {{ partial('layouts/patial_tabs') }} 2 | 3 | 4 | {{ content() }} 5 | -------------------------------------------------------------------------------- /themes/invo/layouts/main.volt: -------------------------------------------------------------------------------- 1 | {% set topMenu = [ 2 | 'index': [ 3 | 'title': 'Home', 4 | 'uri': '/index', 5 | 'with_auth': false 6 | ], 7 | 'invoices': [ 8 | 'title': 'Invoices', 9 | 'uri': '/invoices/index', 10 | 'with_auth': true 11 | ], 12 | 'about': [ 13 | 'title': 'About', 14 | 'uri': '/about/index', 15 | 'with_auth': false 16 | ], 17 | 'contact': [ 18 | 'title': 'Contact', 19 | 'uri': '/contact/index', 20 | 'with_auth': false 21 | ] 22 | ] %} 23 | 24 | 54 | 55 | 56 |
57 | {{ flash.output() }} 58 | {{ content() }} 59 |
60 |
61 |

© Company {{ date('Y') }}

62 |
63 |
64 | -------------------------------------------------------------------------------- /themes/invo/layouts/patial_tabs.volt: -------------------------------------------------------------------------------- 1 | {% set tabs = [ 2 | [ 3 | 'controller': 'invoices', 4 | 'action': 'index', 5 | 'title': 'Invoices', 6 | 'uri': 'invoices/index' 7 | ], 8 | [ 9 | 'controller': 'companies', 10 | 'action': 'index', 11 | 'title': 'Companies', 12 | 'uri': 'companies/index' 13 | ], 14 | [ 15 | 'controller': 'products', 16 | 'action': 'index', 17 | 'title': 'Products', 18 | 'uri': 'products/index' 19 | ], 20 | [ 21 | 'controller': 'producttypes', 22 | 'action': 'index', 23 | 'title': 'Product Types', 24 | 'uri': 'producttypes/index' 25 | ], 26 | [ 27 | 'controller': 'invoices', 28 | 'action': 'profile', 29 | 'title': 'Your Profile', 30 | 'uri': 'invoices/profile' 31 | ] 32 | ] %} 33 | 34 | 43 | -------------------------------------------------------------------------------- /themes/invo/layouts/products.volt: -------------------------------------------------------------------------------- 1 | {{ partial('layouts/patial_tabs') }} 2 | 3 | 4 | {{ content() }} 5 | -------------------------------------------------------------------------------- /themes/invo/layouts/producttypes.volt: -------------------------------------------------------------------------------- 1 | {{ partial('layouts/patial_tabs') }} 2 | 3 | 4 | {{ content() }} 5 | -------------------------------------------------------------------------------- /themes/invo/layouts/register.volt: -------------------------------------------------------------------------------- 1 | 2 | {{ content() }} 3 | -------------------------------------------------------------------------------- /themes/invo/layouts/session.volt: -------------------------------------------------------------------------------- 1 | 2 | {{ content() }} 3 | -------------------------------------------------------------------------------- /themes/invo/products/edit.volt: -------------------------------------------------------------------------------- 1 |
2 |
    3 | 6 |
  • 7 | {{ tag.inputSubmit('Save', 'Save', ['class': 'btn btn-success']) }} 8 |
  • 9 |
10 | 11 |

Edit products

12 | 13 |
14 | {% for element in form %} 15 | {% if is_a(element, 'Phalcon\Forms\Element\Hidden') %} 16 | {{ element }} 17 | {% else %} 18 |
19 | {{ element.label() }} 20 | {{ element.render(['class': 'form-control']) }} 21 |
22 | {% endif %} 23 | {% endfor %} 24 |
25 |
26 | -------------------------------------------------------------------------------- /themes/invo/products/index.volt: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Search products

4 |
5 |
6 | {{ tag.a(url('products/new'), 'Create Product', ['class':'btn btn-primary']) }} 7 |
8 |
9 | 10 |
11 | {% for element in form %} 12 | {% if is_a(element, 'Phalcon\Forms\Element\Hidden') %} 13 | {{ element }} 14 | {% else %} 15 |
16 | {{ element.label() }} 17 |
18 | {{ element.setAttribute("class", "form-control") }} 19 |
20 |
21 | {% endif %} 22 | {% endfor %} 23 | 24 | {{ tag.inputSubmit('Search', null, ['class': 'btn btn-primary', 'id': null, 'name': null, 'value': 'Search']) }} 25 |
26 | -------------------------------------------------------------------------------- /themes/invo/products/new.volt: -------------------------------------------------------------------------------- 1 |
2 |
    3 | 6 |
  • 7 | {{ tag.inputSubmit('Save', 'Save', ['class': 'btn btn-success', 'id':null, 'name':null]) }} 8 |
  • 9 |
10 | 11 |
12 | {% for element in form %} 13 | {% if is_a(element, 'Phalcon\Forms\Element\Hidden') %} 14 | {{ element }} 15 | {% else %} 16 |
17 | {{ element.label() }} 18 | {{ element.render(['class': 'form-control']) }} 19 |
20 | {% endif %} 21 | {% endfor %} 22 |
23 |
24 | -------------------------------------------------------------------------------- /themes/invo/products/search.volt: -------------------------------------------------------------------------------- 1 | 9 | 10 | {% for product in page.items %} 11 | {% if loop.first %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% endif %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% if loop.last %} 34 | 35 | 44 | 45 | 46 |
IdProduct TypeNamePriceActive
{{ product.id }}{{ product.productTypes.name }}{{ product.name }}${{ "%.2f"|format(product.price) }}{{ product.getActiveDetail() }}{{ tag.a(url('products/edit/' ~ product.id), " Edit", ['class': 'btn btn-default'], true) }}{{ tag.a(url('products/delete/' ~ product.id), " Delete", ['class': 'btn btn-default'], true) }}
36 |
37 | {{ tag.a(url('products/search'), " First", ['class': 'btn'], true) }} 38 | {{ tag.a(url('products/search?page=') ~ page.getPrevious(), " Previous", ['class': 'btn'], true) }} 39 | {{ tag.a(url('products/search?page=') ~ page.getNext(), " Next", ['class': 'btn'], true) }} 40 | {{ tag.a(url('products/search?page=') ~ page.getLast(), " Last", ['class': 'btn'], true) }} 41 | {{ page.getCurrent() }} of {{ page.getLast() }} 42 |
43 |
47 | {% endif %} 48 | {% else %} 49 | No products are recorded 50 | {% endfor %} 51 | -------------------------------------------------------------------------------- /themes/invo/producttypes/edit.volt: -------------------------------------------------------------------------------- 1 |
2 |
    3 | 6 |
  • 7 | {{ tag.inputSubmit('Save', 'Save', ['class': 'btn btn-success', 'id': null, 'name': null]) }} 8 |
  • 9 |
10 | 11 |

Edit Product Types

12 | 13 |
14 | {% for element in form %} 15 | {% if is_a(element, 'Phalcon\Forms\Element\Hidden') %} 16 | {{ element }} 17 | {% else %} 18 |
19 | {{ element.label(['class': 'control-label']) }} 20 |
21 | {{ element }} 22 |
23 |
24 | {% endif %} 25 | {% endfor %} 26 |
27 |
28 | -------------------------------------------------------------------------------- /themes/invo/producttypes/index.volt: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Search product types

4 |
5 |
6 | {{ tag.a(url('producttypes/new'), 'Create product types', ['class':'btn btn-primary']) }} 7 |
8 |
9 | 10 |
11 |
12 | 13 | {{ tag.inputNumeric('id', '', ['size': 10, 'maxlength': 10, 'class': 'form-control']) }} 14 |
15 | 16 |
17 | 18 | {{ tag.inputText('name', '', ['size': 24, 'maxlength': 70, 'class': 'form-control']) }} 19 |
20 | 21 | {{ tag.inputSubmit('Search', null, ['class': 'btn btn-primary', 'id': null, 'name': null, 'value': 'Search']) }} 22 |
23 | -------------------------------------------------------------------------------- /themes/invo/producttypes/new.volt: -------------------------------------------------------------------------------- 1 |
2 |
    3 | 6 |
  • 7 | {{ tag.inputSubmit('Save', 'Save', ['class': 'btn btn-success', 'id':null, 'name':null]) }} 8 |
  • 9 |
10 | 11 |
12 |

Create product types

13 | 14 |
15 | 16 | {{ tag.inputText('name', '', ['size': 24, 'maxlength': 70]) }} 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /themes/invo/producttypes/search.volt: -------------------------------------------------------------------------------- 1 | 9 | 10 | {% for producttype in page.items %} 11 | {% if loop.first %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% endif %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% if loop.last %} 28 | 29 | 38 | 39 | 40 |
IdName
{{ producttype.id }}{{ producttype.name }}{{ tag.a(url('producttypes/edit/' ~ producttype.id), " Edit", ['class': 'btn btn-default'], true) }}{{ tag.a(url('producttypes/delete/' ~ producttype.id), " Delete", ['class': 'btn btn-default'], true) }}
30 |
31 | {{ tag.a(url('producttypes/search'), " First", ['class': 'btn'], true) }} 32 | {{ tag.a(url('producttypes/search?page=') ~ page.getPrevious(), " Previous", ['class': 'btn'], true) }} 33 | {{ tag.a(url('producttypes/search?page=') ~ page.getNext(), " Next", ['class': 'btn'], true) }} 34 | {{ tag.a(url('producttypes/search?page=') ~ page.getLast(), " Last", ['class': 'btn'], true) }} 35 | {{ page.getCurrent() }}/{{ page.getLast() }} 36 |
37 |
41 | {% endif %} 42 | {% else %} 43 | No product types are recorded 44 | {% endfor %} 45 | -------------------------------------------------------------------------------- /themes/invo/register/index.volt: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
8 | {{ form.label('name', ['class': 'control-label']) }} 9 |
10 | {{ form.render('name', ['class': 'form-control']) }} 11 |

(required)

12 |
13 | Warning! Please enter your full name 14 |
15 |
16 |
17 | 18 |
19 | {{ form.label('username', ['class': 'control-label']) }} 20 |
21 | {{ form.render('username', ['class': 'form-control']) }} 22 |

(required)

23 |
24 | Warning! Please enter your desired user name 25 |
26 |
27 |
28 | 29 |
30 | {{ form.label('email', ['class': 'control-label']) }} 31 |
32 | {{ form.render('email', ['class': 'form-control']) }} 33 |

(required)

34 |
35 | Warning! Please enter your email 36 |
37 |
38 |
39 | 40 |
41 | {{ form.label('password', ['class': 'control-label']) }} 42 |
43 | {{ form.render('password', ['class': 'form-control']) }} 44 |

(minimum 8 characters)

45 |
46 | Warning! Please provide a valid password 47 |
48 |
49 |
50 | 51 |
52 | 53 |
54 | {{ password_field('repeatPassword', 'class': 'form-control') }} 55 |
56 | Warning! The password does not match 57 |
58 |
59 |
60 | 61 |
62 | {{ tag.inputSubmit('Register', null, ['class': 'btn btn-primary', 'id': null, 'name': null, 'value': 'Register', 'onclick': 'return SignUp.validate();']) }} 63 |

By signing up, you accept terms of use and privacy policy.

64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /themes/invo/session/index.volt: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 | 7 |
8 |
9 |
10 | {{ form.label('email', ['class': 'control-label']) }} 11 |
12 | {{ form.render('email', ['class': 'form-control']) }} 13 |
14 |
15 |
16 | {{ form.label('password', ['class': 'control-label']) }} 17 |
18 | {{ form.render('password', ['class': 'form-control']) }} 19 |
20 |
21 |
22 | {{ tag.inputSubmit('Login', null, ['class': 'btn btn-primary btn-large', 'id': null, 'name': null, 'value': 'Login']) }} 23 |
24 |
25 |
26 |
27 | 28 |
29 | 32 | 33 |

Create an account offers the following advantages:

34 |
    35 |
  • Create, track and export your invoices online
  • 36 |
  • Gain critical insights into how your business is doing
  • 37 |
  • Stay informed about promotions and special packages
  • 38 |
39 | 40 |
41 | {{ tag.a(url('register'), 'Sign Up', ['class':'btn btn-primary btn-large btn-success']) }} 42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /var/cache/volt/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /var/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | --------------------------------------------------------------------------------