├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── jext.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin └── jext-cli ├── composer.json ├── composer.lock ├── docs ├── _config.yml └── index.md ├── install.sh ├── phpcs.xml └── src ├── Application.php ├── Assets ├── component │ ├── administrator │ │ ├── access.jext │ │ ├── component.jext │ │ ├── config.jext │ │ ├── forms │ │ │ ├── filter_notes.jext │ │ │ └── note.jext │ │ ├── helpers │ │ │ └── component.jext │ │ ├── services │ │ │ └── provider.jext │ │ ├── sql │ │ │ └── install.mysql.utf8.jext │ │ ├── src │ │ │ ├── Controller │ │ │ │ ├── DisplayController.jext │ │ │ │ ├── IcomoonController.jext │ │ │ │ ├── NoteController.jext │ │ │ │ └── NotesController.jext │ │ │ ├── Extension │ │ │ │ └── ExtensionComponent.jext │ │ │ ├── Helper │ │ │ │ ├── ExtensionHelper.jext │ │ │ │ └── Icomoon.jext │ │ │ ├── Model │ │ │ │ ├── NoteModel.jext │ │ │ │ └── NotesModel.jext │ │ │ ├── Service │ │ │ │ └── HTML │ │ │ │ │ └── Icon.jext │ │ │ ├── Table │ │ │ │ └── NoteTable.jext │ │ │ └── View │ │ │ │ ├── Icomoon │ │ │ │ └── HtmlView.jext │ │ │ │ ├── Note │ │ │ │ └── HtmlView.jext │ │ │ │ └── Notes │ │ │ │ └── HtmlView.jext │ │ └── tmpl │ │ │ ├── icomoon │ │ │ └── default.jext │ │ │ ├── note │ │ │ └── edit.jext │ │ │ └── notes │ │ │ └── default.jext │ └── site │ │ ├── src │ │ ├── Controller │ │ │ ├── DisplayController.jext │ │ │ └── NotesController.jext │ │ ├── Dispatcher │ │ │ └── Dispatcher.jext │ │ ├── Helper │ │ │ └── RouteHelper.jext │ │ ├── Model │ │ │ ├── NoteModel.jext │ │ │ └── NotesModel.jext │ │ ├── Service │ │ │ └── Router.jext │ │ └── View │ │ │ ├── Note │ │ │ └── HtmlView.jext │ │ │ └── Notes │ │ │ └── HtmlView.jext │ │ └── tmpl │ │ ├── note │ │ └── default.jext │ │ └── notes │ │ ├── default.jext │ │ └── menu.jext ├── injection │ ├── administrator │ │ ├── install.sql.jext │ │ ├── language.jext │ │ ├── language.sys.jext │ │ └── submenu.jext │ ├── media │ │ └── asset.jext │ └── site │ │ ├── menuitem.language.jext │ │ ├── router.jext │ │ └── router.methods.jext ├── language │ ├── administrator │ │ ├── extension.jext │ │ └── extension.sys.jext │ └── site │ │ └── extension.jext ├── media │ ├── css │ │ ├── icons.jext │ │ ├── notes.jext │ │ └── styles.jext │ ├── joomla.asset.jext │ └── js │ │ └── icons.jext └── view │ ├── administrator │ ├── forms │ │ ├── filter.jext │ │ └── form.jext │ ├── src │ │ ├── Controller │ │ │ ├── plural.jext │ │ │ └── singular.jext │ │ ├── Model │ │ │ ├── plural.jext │ │ │ └── singular.jext │ │ ├── Table │ │ │ └── table.jext │ │ └── View │ │ │ ├── {{plural_capitalize}} │ │ │ └── HtmlView.jext │ │ │ └── {{singular_capitalize}} │ │ │ └── HtmlView.jext │ └── tmpl │ │ ├── {{plural}} │ │ └── default.jext │ │ └── {{singular}} │ │ └── edit.jext │ └── site │ ├── src │ ├── Controller │ │ └── plural.jext │ ├── Model │ │ ├── plural.jext │ │ └── singular.jext │ └── View │ │ ├── {{plural_capitalize}} │ │ └── HtmlView.jext │ │ └── {{singular_capitalize}} │ │ └── HtmlView.jext │ └── tmpl │ ├── {{plural}} │ ├── default.jext │ └── menu.jext │ └── {{singular}} │ └── default.jext ├── Controllers ├── BaseController.php ├── ComponentController.php ├── HelpController.php ├── VersionController.php └── ViewController.php ├── Interfaces ├── ControllerInterface.php └── RegistryInterface.php ├── Parsers ├── InjectionParser.php └── SourceParser.php ├── Registry.php └── Utils ├── ComponentHelper.php ├── Printer.php ├── SourceMap.php └── Utils.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: sisylana 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Actual behavior** 24 | What did you get except the expected behavior? 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/jext.yml: -------------------------------------------------------------------------------- 1 | name: JEXT-CLI workflow 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - dev 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | operating-system: [ubuntu-latest] 14 | php-versions: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] 15 | 16 | runs-on: ${{ matrix.operating-system }} 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Get composer cache directory 22 | id: composer-cache 23 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 24 | - name: Cache composer dependencies 25 | uses: actions/cache@v2 26 | with: 27 | path: ${{ steps.composer-cache.outputs.dir }} 28 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 29 | restore-keys: ${{ runner.os }}-composer- 30 | - name: Install dependencies 31 | run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader 32 | - name: phpcs 33 | run: composer run-script phpcs 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | .DS_Store 4 | .vscode/ 5 | 6 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 7 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 8 | # composer.lock 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sajeeb Ahamed (sajeeb07ahamed@gmail.com) 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 | # JEXT-CLI 2 | 3 | ![wideGif](https://user-images.githubusercontent.com/5783354/112375177-2e8cc200-8d0d-11eb-825e-4f9d9560eee9.gif) 4 | 5 | 6 | 7 | **JEXT-CLI** is a command-line application for creating Joomla! 4 components using just a simple command. This tool will help you to reduce your job for creating a component. It will provide you a boilerplate for Joomla! 4 component. 8 | 9 | 10 | # Installation 11 | The installation process is too easy. You can install it from the source code. 12 | 13 | ### Prerequisites 14 | You need `PHP CLI`, and `composer` are installed in your machine before proceeding next steps. 15 | 16 | **Note: These processes are only for Linux and macOS. For Windows, these may not work. If you can implement the installation in a Windows machine, please share the idea.** 17 | 18 | #### Steps: 19 | 1. Clone the repository using git `git clone https://github.com/ahamed/jext-cli.git` **or** [Download Zip](https://github.com/ahamed/jext-cli/archive/v1.0.0-beta.1.zip) from github. 20 | 2. Extract the zip file. 21 | 3. Go to the directory, run `cd jext-cli` 22 | 4. Install project dependencies, run `composer install` 23 | 5. Regenerate the class lists for auto-loading, run `composer dump-autoload -o` 24 | 6. Run `chmod +x install.sh` for making the `install.sh` file executable. 25 | 7. Run `./install.sh`, it will ask for the password, just type your password and hit enter. 26 | 27 | If no error happens, then you are done installing the `jext-cli` tool. Now you can use it globally by using a terminal. 28 | 29 | # Usage 30 | This tool is only for Joomla! 4. So you need to- 31 | 1. [Download](https://downloads.joomla.org) and Install Joomla 4. 32 | 2. Go to the Joomla! project's root directory, run `cd path/to/the/project/root` 33 | 3. Run `jext-cli --version`, if it shows you the version message, then the `jext-cli` is installed correctly in your machine. 34 | 35 | ### Creating a new Joomla! component: 36 | #### Syntax 37 | ```shell 38 | jext-cli --component|-c 39 | ``` 40 | 41 | + For creating a new Joomla! 4 component just run `jext-cli --component `. Here the `` would be replaced by your component name, and the name should be without `com_` prefix. If you add the `com_` prefix, then don't worry, this will be sanitized. 42 | 43 | + After that you will be asking for- 44 | + **Author name** (What is the name of the component author. If skip `jext-cli` will take the current username as the author name.) 45 | + **Author Email** (The email address of the component author. If skip then it will be empty.) 46 | + **Author Url** (The author website url, If skip then it will be empty.) 47 | + **Description** (The component description. If skip then it will be empty.) 48 | + **Copyright** (Copyright information, default `(C) {year}, {Author name}`.) 49 | + **License** (Component license information, default `MIT`) 50 | + **Version** (Component initial version number, default `1.0.0`) 51 | + **Namespace** (The component's namespace, default `Joomla\Component\`. Using the default is recommended.) 52 | + **Do you confirm component creation?** (Hit enter if everything is okay. If not then type `no` and hit enter.) 53 | 54 | Congratulation! You have successfully created your first Joomla! 4 component using `jext-cli` tool. 55 | By default `jext-cli --component ` command creates a component with two default views. One for creating `Notes` and another for showing the list of `Icomoon` icons with live search facilities. If you don't want these views then add a flag `--no-sample-view` with the create command. So the command would be `jext-cli --component --no-sample-view` 56 | ### Creating a view to the component: 57 | You can also add a view to the component. A view comes with all the required `Model`, `Controller` and `View` files. 58 | 59 | #### Syntax: 60 | ```shell 61 | jext-cli --view [-f|--front [-b|--back [-bt|--both]]] 62 | ``` 63 | The third argument is optional. If you skip it, then the view is generated for the backend/administrator. If you want the view for the frontend then the third argument would be `-f` or `--front`. You can also create a view for both frontend and backend. For that use the third argument as `-bt` or `--both`. 64 | 65 | #### Available commands 66 | 1. `jext-cli --view` - for creating a view for the administrator part. 67 | 2. `jext-cli --view -b|--back` - same as command 1. 68 | 3. `jext-cli --view -f|--front` - for creating a view for the frontend. 69 | 4. `jext-cli --view -bt|--both` - for creating a view for both of the administrator or frontend. 70 | 71 | This command will ask you for some information. 72 | - [required] **Component Name** (The component name where to put the view) 73 | - [required] **View name (Singular)** (The singular name of the view) 74 | - [optional] **View name (Plural)** (The plural name of the view. The `jext-cli` will predict the plural name from the singular name. If you think the prediction is correct then hit enter, otherwise enter the plural name and hit enter.) 75 | 76 | Congratulations! You have successfully generated a view. 77 | Using these commands create all the views you've required. 78 | 79 | Now it's time to check our component. We have to install the component first. 80 | For installing the component- 81 | 1. Login as a super user to the administrator panel. 82 | 2. Go to `Settings > Discover` 83 | 3. Select the component to install and click the `Install` button from the toolbar. 84 | 4. The component is installed. For checking this go to the `Component` menu from the left sidebar. 85 | 86 | __Note: If you install the component after adding all the views, 87 | then all the database tables related with the views will be installed along with 88 | the component. Also you can find the views as the submenu of the component. But if you add any view 89 | after installing the component, then you have to create a database table associate with the view. 90 | The jext-cli tool assumes that every view has a database table. For getting the table 91 | definition check the install.sql file inside the administrator component folder of the component.__ 92 | 93 | ___For more information run `jext-cli --help` or `jext-cli -h` or just `jext-cli` and hit enter.___ 94 | 95 | # Contribution 96 | For contribution- 97 | 1. Fork the repository. 98 | 2. Clone the forked repository to your local machine. 99 | 3. Create a new branch from the `main` branch, e.g. `git checkout -b new_branch`. [Note never work at main if you plan to contribute. Never means never.] 100 | 4. Commit your changes and push to your remote. 101 | 5. Make a pull request (PR) to this repository. 102 | 103 | # Test 104 | Currently `phpcs` testing is integrated. If you interested and make a PR then first make sure that you pass the test of `phpcs`. Testing this is simple, just run- 105 | 106 | ```console 107 | composer run-script phpcs 108 | ``` 109 | 110 | # Support 111 | If you get any problems then raise an issue [here](https://github.com/ahamed/jext-cli/issues) or send me at [sajeeb07ahamed@gmail.com](mailto:sajeeb07ahamed@gmail.com) but first option is preferable. 112 | -------------------------------------------------------------------------------- /bin/jext-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run($argv); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ahamed/jext-cli", 3 | "description": "This is a cli tool for building joomla component", 4 | "type": "library", 5 | "license": "MIT", 6 | "version": "1.0.0", 7 | "authors": [ 8 | { 9 | "name": "Sajeeb Ahamed", 10 | "email": "sajeeb07ahamed@gmail.com" 11 | } 12 | ], 13 | "scripts": { 14 | "phpcs": "./vendor/bin/phpcs --extensions=php -p ." 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Ahamed\\Jext\\": "src/" 19 | } 20 | }, 21 | "require-dev": { 22 | "squizlabs/php_codesniffer": "*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "68f0ad47378c538eaa2a01c68f60f156", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "squizlabs/php_codesniffer", 12 | "version": "3.5.8", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 16 | "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", 21 | "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "ext-simplexml": "*", 26 | "ext-tokenizer": "*", 27 | "ext-xmlwriter": "*", 28 | "php": ">=5.4.0" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 32 | }, 33 | "bin": [ 34 | "bin/phpcs", 35 | "bin/phpcbf" 36 | ], 37 | "type": "library", 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "3.x-dev" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "BSD-3-Clause" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Greg Sherwood", 50 | "role": "lead" 51 | } 52 | ], 53 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 54 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", 55 | "keywords": [ 56 | "phpcs", 57 | "standards" 58 | ], 59 | "support": { 60 | "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", 61 | "source": "https://github.com/squizlabs/PHP_CodeSniffer", 62 | "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" 63 | }, 64 | "time": "2020-10-23T02:01:07+00:00" 65 | } 66 | ], 67 | "aliases": [], 68 | "minimum-stability": "stable", 69 | "stability-flags": [], 70 | "prefer-stable": false, 71 | "prefer-lowest": false, 72 | "platform": [], 73 | "platform-dev": [], 74 | "plugin-api-version": "2.0.0" 75 | } 76 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: "JEXT CLI" 3 | description: "JEXT CLI is a component builder tool for Joomla! By using this command line tool you can easily build a Joomla! 4 component by just write a simple command." 4 | show_downloads: "true" 5 | google_analytics: "G-CY134LGBYY" 6 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **JEXT-CLI** is a command line application for creating Joomla! 4 component using just a simple command. This tool will help you to reduce your job for creating a component. It will provide you a boilerplate for Joomla! 4 component. 4 | 5 | Author: Sajeeb Ahamed (sajeeb07ahamemd@gmail.com) 6 | 7 | Tags: #joomla, #joomla-component-builder, #joomla-extension-builder, #jext-cli, #php-cli, #cli, #components, #extensions. 8 | 9 | Hello, all the Joomla extension developers. Today I am going to share with you a nice CLI application for generating Joomla! 4 components. So, tie your belt, and let's start the journey. 10 | 11 | The stable version of Joomla! 4 is going to release very soon. And as it is a major release so there are some major changes that may break your existing extensions. So it would be hard for the developers to upgrade the extensions from Joomla! 3 to 4. 12 | Now in this statement, you may disagree with me that upgrading from 3 to 4 is not that much hard. If one already makes compatible his/her extension for `3.9.x` then it's easy. I also agree with you. But Joomla! 4 brings a newly organized component structure for you. What if you want to taste the original flavor? You need to do more. 13 | 14 | So, in this situation, I am here to introduce you to a new CLI application. The application helps you to create a component by just running a simple command in the terminal. This will provide you a boilerplate of a structured Joomla! 4 component. 15 | 16 | By using this you can create a new component and also brings your business logic from your existing components after creating a new component (as J4 still provides you MVC structure). 17 | 18 | Here the question is how to use it? It's quite easy. Just follow me- 19 | 20 | > For using the `jext-cli` you need **php-cli** and **composer** installed in your machine. The installation is written for Linux & Mac users. For Windows users, this is not going to work. 21 | 22 | ### Installation 23 | 1. Clone the Github repository `git clone https://github.com/ahamed/jext-cli.git` 24 | 2. Navigate to the directory, `cd jext-cli` 25 | 3. Install the project dependencies, `composer install` 26 | 4. Update the auto-loading classes, `composer dump-autoload -o` 27 | 5. Install the CLI tool, `./install.sh` (for macOS & Linux) 28 | 6. Check if the tool is installed, `jext-cli --help` 29 | 30 | If you find the manuscript that means the `jext-cli` is installed on your machine globally. 31 | 32 | ### Usage 33 | You can create components and views for a component now. For creating a component first navigate to your Joomla! 4 project root directory, `cd path/to/your/project/root`, then run- 34 | 35 | ```console 36 | jext-cli --component|-c 37 | ``` 38 | Here, `--component|-c` means any of `--component` or `-c`, and the `` is the name of your component. Please don't use a multi-word name as a component name. 39 | 40 | This command will ask for some basic meta-information. They are- 41 | + **Author name** (What is the name of the component author. If skip `jext-cli` will take the current username as author name.) 42 | 43 | + **Author Email** (The email address of the component author. If skip then it will be empty.) 44 | + **Author Url** (The author website url, If skip then it will be empty.) 45 | + **Description** (The component description. If skip then it will be empty.) 46 | + **Copyright** (Copyright information, default `(C) {year}, {Author name}`.) 47 | + **License** (Component license information, default `MIT`) 48 | + **Version** (Component initial version number, default `1.0.0`) 49 | + **Namespace** (The component's namespace, default `Joomla\Component\`. Using the default is recommended.) 50 | + **Do you confirm component creation?** (Hit enter if everything is okay. If not then type `no` and hit enter.) 51 | 52 | Fill them up correctly and this will create the component. 53 | 54 | _By default, the **JEXT-CLI** creates a component with two sample views. If you don't want to create the views with the component then use `--no-sample-view` flag with it. For example-_ 55 | 56 | ```console 57 | jext-cli --component --no-sample-view 58 | ``` 59 | 60 | Here you successfully created your component. Now we know all components have one or more views. The views are categorized into two types. Administrator view and site view. Here the administrator views are called `back` views, and the site views are called `front` views. For creating a view, the command is- 61 | 62 | ```console 63 | jext-cli --view [--back|-b, [--front|-f, [--both|-bt]]] 64 | ``` 65 | 66 | Here, the `[--back|-b, [--front|-f, [--both|-bt]]]` means any of the six options. If I describe the options, then they are- 67 | 68 | - `--back|-b` means either `--back` or `-b` which stands for the back view or the administrator view. 69 | - `--front|-f` means either `--front` or `-f` which stands for the front view or the site view. 70 | - `--both|-bt` means either `--both` or `-bt` stands for both administrator and site views. 71 | 72 | After running any of the commands the application asks you the component name. This indicates for which component you are going to create a view. 73 | 74 | After putting the valid component name the system will ask you for the view's names. You have to enter two names for a view. One is the singular name and another is the plural name. The `JEXT-CLI` can predict the plural name after you entering the singular name. If you think the prediction is correct then just hit enter otherwise, enter the plural name. That's all for creating a view. All the related (controller, model, view) files are created for you. This also injects the required language strings, SQL queries, and other required codes for making the view functional. 75 | 76 | Your view is created. You can create as many views as you need. After creating all the views log in as `administrator` to the project then go to `Settings > Discover`. There you can see the component waits for you to install. Select the component and click the `Install` button from the toolbar. 77 | 78 | Hurra! the component has been installed. Now from the sidebar, go to `Components > Your component`. Here you get your views. -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d ~/.jext-cli ] 4 | then 5 | mkdir ~/.jext-cli 6 | fi 7 | 8 | # Copy the project files to the include directory. 9 | cp -r * ~/.jext-cli 10 | 11 | if [ -L /usr/local/bin/jext-cli ] 12 | then 13 | sudo rm /usr/local/bin/jext-cli 14 | fi 15 | 16 | sudo ln -s ~/.jext-cli/bin/jext-cli /usr/local/bin/jext-cli 17 | 18 | tput setaf 2; echo "Installation Completed Successfully!: "$1; tput sgr0; 19 | tput setaf 3; echo "Welcome to Jext-CLI: "$1; tput sgr0; 20 | echo "You are using: " 21 | jext-cli --version 22 | tput setaf 2; echo "You can perform these operations: "$1; tput sgr0; 23 | jext-cli --help -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext; 10 | 11 | use Ahamed\Jext\Controllers\ComponentController; 12 | use Ahamed\Jext\Controllers\HelpController; 13 | use Ahamed\Jext\Controllers\VersionController; 14 | use Ahamed\Jext\Controllers\ViewController; 15 | use Ahamed\Jext\Registry; 16 | 17 | /** 18 | * The main CLI Application class. 19 | * 20 | * @since 1.0.0 21 | */ 22 | class Application 23 | { 24 | /** 25 | * Command Registry instance. 26 | * 27 | * @var CommandRegistry $registry the registry instance. 28 | * 29 | * @since 1.0.0 30 | */ 31 | protected $registry = null; 32 | 33 | /** 34 | * Set the default command for the application. 35 | * 36 | * @var string $defaultCommand The default command name. 37 | * 38 | * @since 1.0.0 39 | */ 40 | protected $defaultCommand = '--help'; 41 | 42 | /** 43 | * The application constructor function 44 | * 45 | * @since 1.0.0 46 | */ 47 | public function __construct() 48 | { 49 | $this->registry = new Registry; 50 | } 51 | 52 | /** 53 | * Run commander function. 54 | * 55 | * @param array $argv The CLI arguments array. 56 | * 57 | * @return void 58 | * 59 | * @since 1.0.0 60 | */ 61 | public function run(array $argv = []) 62 | { 63 | $command = $argv[1] ?? $this->defaultCommand; 64 | 65 | switch ($command) 66 | { 67 | case '--component': 68 | case '-c': 69 | $this->registry->registerController($command, new ComponentController); 70 | \call_user_func($this->registry->getRegistry($command), $argv); 71 | break; 72 | 73 | case '--view': 74 | $this->registry->registerController($command, new ViewController); 75 | \call_user_func($this->registry->getRegistry($command), $argv); 76 | break; 77 | 78 | case '--version': 79 | case '-v': 80 | $this->registry->registerController($command, new VersionController); 81 | \call_user_func($this->registry->getRegistry($command), $argv); 82 | break; 83 | 84 | case '--help': 85 | case '-h': 86 | default: 87 | $this->registry->registerController($command, new HelpController); 88 | \call_user_func($this->registry->getRegistry($command), $argv); 89 | break; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/access.jext: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/component.jext: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{prefix_component}} 4 | {{author}} 5 | {{creationDate}} 6 | {{copyright}} 7 | {{license}} 8 | {{email}} 9 | {{url}} 10 | {{version}} 11 | {{prefix_component_uppercase}}_XML_DESCRIPTION 12 | {{namespace}} 13 | 14 | 15 | 16 | 17 | sql/install.mysql.utf8.sql 18 | 19 | 20 | 21 | 22 | forms 23 | helpers 24 | src 25 | tmpl 26 | 27 | 28 | 29 | language/en-GB/{{prefix_component}}.ini 30 | 31 | 32 | 33 | js 34 | 35 | 36 | 37 | {{prefix_component_uppercase}} 38 | 39 | 43 | {{prefix_component_uppercase}}_NOTES 45 | {{prefix_component_uppercase}}_ICOMOON 47 | 48 | 49 | 50 | access.xml 51 | config.xml 52 | {{component}}.xml 53 | forms 54 | helpers 55 | services 56 | sql 57 | src 58 | tmpl 59 | 60 | 61 | language/en-GB/{{prefix_component}}.ini 62 | language/en-GB/{{prefix_component}}.sys.ini 63 | 64 | 65 | 66 | {{component}} 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/config.jext: -------------------------------------------------------------------------------- 1 | 2 | 3 |
8 | 9 |
10 |
15 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/forms/filter_notes.jext: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 12 | 13 | 20 | 21 | 22 | 23 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | 81 |
-------------------------------------------------------------------------------- /src/Assets/component/administrator/forms/note.jext: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 12 | 13 | 18 | 19 | 27 | 28 | 36 | 37 | 45 | 46 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 66 | 67 | 72 | 73 | 79 | 80 | 89 | 90 | 99 | 100 | 111 | 112 | 119 | 120 | 125 | 126 | 127 |
128 | Built 129 |
130 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/helpers/component.jext: -------------------------------------------------------------------------------- 1 | registerServiceProvider(new MVCFactory('{{namespace}}')); 44 | $container->registerServiceProvider(new ComponentDispatcherFactory('{{namespace}}')); 45 | $container->registerServiceProvider(new RouterFactory('{{namespace}}')); 46 | 47 | $container->set( 48 | ComponentInterface::class, 49 | function (Container $container) 50 | { 51 | $component = new {{component_capitalize}}Component($container->get(ComponentDispatcherFactoryInterface::class)); 52 | 53 | $component->setRegistry($container->get(Registry::class)); 54 | $component->setMVCFactory($container->get(MVCFactoryInterface::class)); 55 | $component->setRouterFactory($container->get(RouterFactoryInterface::class)); 56 | 57 | return $component; 58 | } 59 | ); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/sql/install.mysql.utf8.jext: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `#__{{component}}_notes` ( 2 | `id` bigint NOT NULL AUTO_INCREMENT, 3 | `asset_id` bigint NOT NULL DEFAULT 0, 4 | `title` varchar(255) NOT NULL DEFAULT '', 5 | `alias` varchar(300) NOT NULL DEFAULT '', 6 | `description` text, 7 | `published` tinyint(1) NOT NULL DEFAULT 0, 8 | `checked_out` int unsigned NOT NULL DEFAULT 0, 9 | `checked_out_time` datetime, 10 | `ordering` int NOT NULL DEFAULT 0, 11 | `access` int unsigned NOT NULL DEFAULT 0, 12 | `language` varchar(7) NOT NULL DEFAULT '*', 13 | `created` datetime NOT NULL, 14 | `created_by` int unsigned NOT NULL DEFAULT 0, 15 | `modified` datetime NOT NULL, 16 | `modified_by` int unsigned NOT NULL DEFAULT 0, 17 | PRIMARY KEY (`id`) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; 19 | 20 | --{{inject: install_table}} -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Controller/DisplayController.jext: -------------------------------------------------------------------------------- 1 | input->get('view', 'notes'); 46 | $layout = $this->input->get('layout', 'default'); 47 | $id = $this->input->getInt('id', 0); 48 | 49 | // Check for edit form. 50 | if ($view === 'note' && $layout === 'edit' && !$this->checkEditId('{{prefix_component}}.edit.note', $id)) 51 | { 52 | // Somehow the person just went to the form - we don't allow that. 53 | $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error'); 54 | $this->setRedirect(Route::_('index.php?option={{prefix_component}}&view=notes', false)); 55 | 56 | return false; 57 | } 58 | 59 | return parent::display(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Controller/IcomoonController.jext: -------------------------------------------------------------------------------- 1 | input->getString('keyword', ''); 55 | $icons = Icomoon::getIcons($keyword); 56 | 57 | $html = []; 58 | 59 | if (!empty($icons)) 60 | { 61 | foreach ($icons as $icon) 62 | { 63 | $html[] = ''; 64 | $html[] = '
'; 65 | $html[] = HTMLHelper::_('icomoon.render', $icon, 'preview'); 66 | $html[] = '
'; 67 | $html[] = '
'; 68 | $html[] = ' ' . $icon . ''; 69 | $html[] = '
'; 70 | $html[] = ' Copied!'; 71 | $html[] = '
'; 72 | } 73 | } 74 | else 75 | { 76 | $html[] = '
'; 77 | $html[] = ''; 78 | $html[] = '

No Icon Found!

'; 79 | $html[] = '
'; 80 | } 81 | 82 | $this->app->setHeader('status', 200, true); 83 | $this->app->sendHeaders(); 84 | echo new JsonResponse(['html' => implode("\n", $html), 'total' => count($icons)]); 85 | $this->app->close(); 86 | } 87 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Controller/NoteController.jext: -------------------------------------------------------------------------------- 1 | app->getIdentity(); 72 | 73 | return $user->authorise('core.create', '{{prefix_component}}.note'); 74 | } 75 | 76 | /** 77 | * Method override to check if you can edit an existing record. 78 | * 79 | * @param array $data An array of input data. 80 | * @param string $key The name of the key for the primary key. 81 | * 82 | * @return boolean 83 | * 84 | * @since 1.0.0 85 | */ 86 | protected function allowEdit($data = array(), $key = 'id') 87 | { 88 | $recordId = (int) isset($data[$key]) ? $data[$key] : 0; 89 | $user = $this->app->getIdentity(); 90 | 91 | // Zero record (id:0), return component edit permission by calling parent controller method 92 | if (!$recordId) 93 | { 94 | return parent::allowEdit($data, $key); 95 | } 96 | 97 | // Check edit on the record asset (explicit or inherited) 98 | if ($user->authorise('core.edit', '{{prefix_component}}.note.' . $recordId)) 99 | { 100 | return true; 101 | } 102 | 103 | // Check edit own on the record asset (explicit or inherited) 104 | if ($user->authorise('core.edit.own', '{{prefix_component}}.note.' . $recordId)) 105 | { 106 | // Existing record already has an owner, get it 107 | $record = $this->getModel()->getItem($recordId); 108 | 109 | if (empty($record)) 110 | { 111 | return false; 112 | } 113 | 114 | // Grant if current user is owner of the record 115 | return $user->id === $record->created_by; 116 | } 117 | 118 | return false; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Controller/NotesController.jext: -------------------------------------------------------------------------------- 1 | true)) 53 | { 54 | return parent::getModel($name, $prefix, $config); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Extension/ExtensionComponent.jext: -------------------------------------------------------------------------------- 1 | getRegistry()->register('icomoon', new Icon); 51 | } 52 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Helper/ExtensionHelper.jext: -------------------------------------------------------------------------------- 1 | id) || $record->published !== -2) 57 | { 58 | return false; 59 | } 60 | 61 | return Factory::getApplication()->getIdentity()->authorise('core.delete', '{{prefix_component}}.note.' . (int) $record->id); 62 | } 63 | 64 | /** 65 | * Method to test whether a record can have its state edited. 66 | * 67 | * @param object $record A record object. 68 | * 69 | * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component. 70 | * 71 | * @since {{version}} 72 | */ 73 | protected function canEditState($record) 74 | { 75 | // Check against the category. 76 | if (!empty($record->id)) 77 | { 78 | return Factory::getApplication()->getIdentity()->authorise('core.edit.state', '{{prefix_component}}.note.' . (int) $record->id); 79 | } 80 | 81 | // Default to component settings if category not known. 82 | return parent::canEditState($record); 83 | } 84 | 85 | /** 86 | * Method to check if you can save a record. 87 | * 88 | * @param array $data An array of input data. 89 | * @param string $key The name of the key for the primary key. 90 | * 91 | * @return boolean 92 | * 93 | * @since {{version}} 94 | */ 95 | protected function canSave($data = array(), $key = 'id') 96 | { 97 | if (empty($data[$key])) 98 | { 99 | return false; 100 | } 101 | 102 | return Factory::getApplication()->getIdentity()->authorise('core.edit', '{{prefix_component}}.note.' . $data[$key]); 103 | } 104 | 105 | /** 106 | * Method to get the row form. 107 | * 108 | * @param array $data Data for the form. 109 | * @param boolean $loadData True if the form is to load its own data (default case), false if not. 110 | * 111 | * @return Form|boolean A Form object on success, false on failure 112 | * 113 | * @since {{version}} 114 | */ 115 | public function getForm($data = array(), $loadData = true) 116 | { 117 | // Get the form. 118 | $form = $this->loadForm('{{prefix_component}}.' . $this->formName, $this->formName, array('control' => 'jform', 'load_data' => $loadData)); 119 | 120 | if (empty($form)) 121 | { 122 | return false; 123 | } 124 | 125 | return $form; 126 | } 127 | 128 | /** 129 | * Method to get the data that should be injected in the form. 130 | * 131 | * @return mixed The data for the form. 132 | * 133 | * @since {{version}} 134 | */ 135 | protected function loadFormData() 136 | { 137 | $app = Factory::getApplication(); 138 | 139 | // Check the session for previously entered form data. 140 | $data = $app->getUserState('{{prefix_component}}.edit.note.data', array()); 141 | 142 | if (empty($data)) 143 | { 144 | $data = $this->getItem(); 145 | 146 | // Prime some default values. 147 | if ($this->getState('note.id') === 0) 148 | { 149 | $data->set('id', $app->input->get('id', $app->getUserState('{{prefix_component}}.notes.filter.id'), 'int')); 150 | } 151 | } 152 | 153 | $this->preprocessData('{{prefix_component}}.note', $data); 154 | 155 | return $data; 156 | } 157 | 158 | /** 159 | * Method to save the form data. 160 | * 161 | * @param array $data The form data. 162 | * 163 | * @return boolean True on success. 164 | * 165 | * @since {{version}} 166 | */ 167 | public function save($data) 168 | { 169 | $input = Factory::getApplication()->getInput(); 170 | 171 | // Alter the name for save as copy 172 | if ($input->get('task') == 'save2copy') 173 | { 174 | $origTable = clone $this->getTable(); 175 | $origTable->load($input->getInt('id')); 176 | 177 | if ($data['title'] === $origTable->title) 178 | { 179 | list($title, $alias) = $this->generateUniqueTitleAlias($data['alias'], $data['title']); 180 | $data['title'] = $title; 181 | $data['alias'] = $alias; 182 | } 183 | else 184 | { 185 | if ($data['alias'] === $origTable->alias) 186 | { 187 | $data['alias'] = ''; 188 | } 189 | } 190 | 191 | $data['published'] = 0; 192 | } 193 | 194 | return parent::save($data); 195 | } 196 | 197 | /** 198 | * Method to change the published state of one or more records. 199 | * Your can perform your own logic before publish/unpublish the record item. 200 | * 201 | * @param array $pks A list of the primary keys to change. 202 | * @param integer $value The value of the published state. 203 | * 204 | * @return boolean True on success. 205 | * 206 | * @since {{version}} 207 | */ 208 | public function publish(&$pks, $value = 1) 209 | { 210 | return parent::publish($pks, $value); 211 | } 212 | 213 | /** 214 | * Generate new alias & title values if duplicate alias found. 215 | * 216 | * @param string $alias The alias string. 217 | * @param string $title The title string. 218 | * 219 | * @return array The updated title, alias array. 220 | * 221 | * @since {{version}} 222 | */ 223 | private function generateUniqueTitleAlias(string $alias, string $title) : array 224 | { 225 | $table = $this->getTable(); 226 | 227 | while ($table->load(['alias' => $alias])) 228 | { 229 | if ($title === $table->title) 230 | { 231 | $title = StringHelper::increment($title); 232 | } 233 | 234 | $alias = StringHelper::increment($alias, 'dash'); 235 | } 236 | 237 | return [$title, $alias]; 238 | } 239 | 240 | /** 241 | * Prepare and sanitize the table prior to saving. 242 | * 243 | * @param \Joomla\CMS\Table\Table $table The Table object 244 | * 245 | * @return void 246 | * 247 | * @since {{version}} 248 | */ 249 | protected function prepareTable($table) 250 | { 251 | } 252 | 253 | /** 254 | * A protected method to get a set of ordering conditions. 255 | * 256 | * @param Table $table A Table object. 257 | * 258 | * @return array An array of conditions to add to ordering queries. 259 | * 260 | * @since {{version}} 261 | */ 262 | protected function getReorderConditions($table) 263 | { 264 | return []; 265 | } 266 | 267 | /** 268 | * Preprocess the form. 269 | * 270 | * @param Form $form Form object. 271 | * @param object $data Data object. 272 | * @param string $group Group name. 273 | * 274 | * @return void 275 | * 276 | * @since {{version}} 277 | */ 278 | protected function preprocessForm(Form $form, $data, $group = '{{component}}') 279 | { 280 | parent::preprocessForm($form, $data, $group); 281 | } 282 | 283 | /** 284 | * Method to get a single record. 285 | * 286 | * @param integer $pk The id of the primary key. 287 | * 288 | * @return mixed Object on success, false on failure. 289 | * 290 | * @since {{version}} 291 | */ 292 | public function getItem($pk = null) 293 | { 294 | return parent::getItem($pk); 295 | } 296 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Model/NotesModel.jext: -------------------------------------------------------------------------------- 1 | getInput()->get('forcedLanguage', '', 'cmd'); 73 | 74 | // Adjust the context to support modal layouts. 75 | if ($layout = $app->input->get('layout')) 76 | { 77 | $this->context .= '.' . $layout; 78 | } 79 | 80 | // Adjust the context to support forced languages. 81 | if ($forcedLanguage) 82 | { 83 | $this->context .= '.' . $forcedLanguage; 84 | } 85 | 86 | // List state information. 87 | parent::populateState($ordering, $direction); 88 | 89 | // Force a language. 90 | if (!empty($forcedLanguage)) 91 | { 92 | $this->setState('filter.language', $forcedLanguage); 93 | } 94 | } 95 | 96 | /** 97 | * Method to get a store id based on model configuration state. 98 | * 99 | * This is necessary because the model is used by the component and 100 | * different modules that might need different sets of data or different 101 | * ordering requirements. 102 | * 103 | * @param string $id A prefix for the store id. 104 | * 105 | * @return string A store id. 106 | * 107 | * @since {{version}} 108 | */ 109 | protected function getStoreId($id = '') 110 | { 111 | // Compile the store id. 112 | $id .= ':' . $this->getState('filter.search'); 113 | $id .= ':' . $this->getState('filter.published'); 114 | $id .= ':' . $this->getState('filter.access'); 115 | $id .= ':' . $this->getState('filter.language'); 116 | $id .= ':' . serialize($this->getState('filter.tag')); 117 | $id .= ':' . $this->getState('filter.level'); 118 | 119 | return parent::getStoreId($id); 120 | } 121 | 122 | /** 123 | * Build an SQL query to load the list data. 124 | * 125 | * @return \JDatabaseQuery 126 | * 127 | * @since {{version}} 128 | */ 129 | protected function getListQuery() 130 | { 131 | $container = Factory::getContainer(); 132 | $app = Factory::getApplication(); 133 | $db = $container->get('DatabaseDriver'); 134 | $query = $db->getQuery(true); 135 | $user = $app->getIdentity(); 136 | 137 | $query->select( 138 | $db->quoteName( 139 | explode( 140 | ', ', 141 | $this->getState( 142 | 'list.select', 143 | 'a.id, a.title, a.alias, a.description, a.published, a.access, a.created, a.created_by, a.ordering, a.language, ' . 144 | 'a.checked_out, a.checked_out_time' 145 | ) 146 | ) 147 | ) 148 | ); 149 | 150 | $query->from($db->quoteName('#__{{component}}_notes', 'a')); 151 | 152 | $query->select($db->quoteName('l.title', 'language_title')) 153 | ->select($db->quoteName('l.image', 'language_image')) 154 | ->join( 155 | 'LEFT', 156 | $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language') 157 | ); 158 | 159 | // Join over the users for the checked out user. 160 | $query->select($db->quoteName('uc.name', 'editor')) 161 | ->join( 162 | 'LEFT', 163 | $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out') 164 | ); 165 | 166 | $query->select($db->quoteName('ua.name', 'author_name')) 167 | ->join( 168 | 'LEFT', 169 | $db->quoteName('#__users', 'ua') . ' ON ' . $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by') 170 | ); 171 | 172 | // Join over the asset groups. 173 | $query->select($db->quoteName('ag.title', 'access_level')) 174 | ->join( 175 | 'LEFT', 176 | $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access') 177 | ); 178 | 179 | // Filter by access level. 180 | if ($access = $this->getState('filter.access')) 181 | { 182 | $query->where($db->quoteName('a.access') . ' = :access'); 183 | $query->bind(':access', $access, ParameterType::INTEGER); 184 | } 185 | 186 | // Implement View Level Access 187 | if (!$user->authorise('core.admin')) 188 | { 189 | $query->whereIn($db->quoteName('a.access'), $user->getAuthorisedViewLevels()); 190 | } 191 | 192 | // Filter by published state 193 | $published = (string) $this->getState('filter.published'); 194 | 195 | if (is_numeric($published)) 196 | { 197 | $query->where($db->quoteName('a.published') . ' = :published'); 198 | $query->bind(':published', $published, ParameterType::INTEGER); 199 | } 200 | elseif ($published === '') 201 | { 202 | $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)'); 203 | } 204 | 205 | // Filter by search in name. 206 | $search = $this->getState('filter.search'); 207 | 208 | if (!empty($search)) 209 | { 210 | if (stripos($search, 'id:') === 0) 211 | { 212 | $search = substr($search, 3); 213 | $query->where($db->quoteName('a.id') . ' = :id'); 214 | $query->bind(':id', $search, ParameterType::INTEGER); 215 | } 216 | else 217 | { 218 | $search = '%' . trim($search) . '%'; 219 | $query->where( 220 | '(' . $db->quoteName('a.title') . ' LIKE :title OR ' . $db->quoteName('a.alias') . ' LIKE :alias)' 221 | ); 222 | $query->bind(':title', $search); 223 | $query->bind(':alias', $search); 224 | } 225 | } 226 | 227 | // Filter on the language. 228 | if ($language = $this->getState('filter.language')) 229 | { 230 | $query->where($db->quoteName('a.language') . ' = :language'); 231 | $query->bind(':language', $language); 232 | } 233 | 234 | // Add the list ordering clause. 235 | $orderCol = $this->state->get('list.ordering', 'a.title'); 236 | $orderDirn = $this->state->get('list.direction', 'asc'); 237 | 238 | if ($orderCol === 'a.ordering') 239 | { 240 | $orderCol = $db->quoteName('a.ordering'); 241 | } 242 | 243 | $query->order($db->escape($orderCol . ' ' . $orderDirn)); 244 | 245 | return $query; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Service/HTML/Icon.jext: -------------------------------------------------------------------------------- 1 | '; 54 | } 55 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/Table/NoteTable.jext: -------------------------------------------------------------------------------- 1 | toSql(); 69 | $userId = Factory::getApplication()->getIdentity()->get('id'); 70 | 71 | // Set created date if not set. 72 | if (!(int) $this->created) 73 | { 74 | $this->created = $date; 75 | } 76 | 77 | if ($this->id) 78 | { 79 | // Existing item 80 | $this->modified_by = $userId; 81 | $this->modified = $date; 82 | } 83 | else 84 | { 85 | // Field created_by field can be set by the user, so we don't touch it if it's set. 86 | if (empty($this->created_by)) 87 | { 88 | $this->created_by = $userId; 89 | } 90 | 91 | if (!(int) $this->modified) 92 | { 93 | $this->modified = $date; 94 | } 95 | 96 | if (empty($this->modified_by)) 97 | { 98 | $this->modified_by = $userId; 99 | } 100 | } 101 | 102 | // Verify that the alias is unique 103 | $table = Table::getInstance('NoteTable', __NAMESPACE__ . '\\', array('dbo' => $this->getDbo())); 104 | $table->load(array('alias' => $this->alias)); 105 | 106 | if ($table->load(array('alias' => $this->alias)) && ($table->id !== $this->id || $this->id === 0)) 107 | { 108 | $this->setError(Text::_('{{prefix_component_uppercase}}_ERROR_UNIQUE_ALIAS')); 109 | 110 | return false; 111 | } 112 | 113 | return parent::store($updateNulls); 114 | } 115 | 116 | /** 117 | * Overloaded check function 118 | * 119 | * @return boolean True on success, false on failure 120 | * 121 | * @see \JTable::check 122 | * @since 1.5 123 | */ 124 | public function check() 125 | { 126 | try 127 | { 128 | parent::check(); 129 | } 130 | catch (\Exception $e) 131 | { 132 | $this->setError($e->getMessage()); 133 | 134 | return false; 135 | } 136 | 137 | // Generate a valid alias 138 | $this->generateAlias(); 139 | 140 | // Sanity check for user_id 141 | if (!$this->created_by) 142 | { 143 | $this->created_by = 0; 144 | } 145 | 146 | if (!$this->modified) 147 | { 148 | $this->modified = $this->created; 149 | } 150 | 151 | if (empty($this->modified_by)) 152 | { 153 | $this->modified_by = $this->created_by; 154 | } 155 | 156 | return true; 157 | } 158 | 159 | /** 160 | * Generate a valid alias from title / date. 161 | * Remains public to be able to check for duplicated alias before saving 162 | * 163 | * @return string 164 | */ 165 | public function generateAlias() 166 | { 167 | if (empty($this->alias)) 168 | { 169 | $this->alias = $this->title; 170 | } 171 | 172 | $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language); 173 | 174 | if (trim(str_replace('-', '', $this->alias)) === '') 175 | { 176 | $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); 177 | } 178 | 179 | return $this->alias; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/View/Icomoon/HtmlView.jext: -------------------------------------------------------------------------------- 1 | get('Errors'); 37 | 38 | // Check for errors. 39 | if (count((array)$errors = $this->get('Errors'))) 40 | { 41 | throw new GenericDataException(implode("\n", $errors), 500); 42 | } 43 | 44 | return parent::display($tpl); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/View/Note/HtmlView.jext: -------------------------------------------------------------------------------- 1 | form = $this->get('Form'); 63 | $this->item = $this->get('Item'); 64 | $this->state = $this->get('State'); 65 | 66 | // Check for errors. 67 | if ((count($errors = $this->get('Errors')))) 68 | { 69 | throw new GenericDataException(implode("\n", $errors), 500); 70 | } 71 | 72 | $this->addToolbar(); 73 | 74 | return parent::display($tpl); 75 | } 76 | 77 | /** 78 | * Add the page title and toolbar. 79 | * 80 | * @return void 81 | * 82 | * @since {{version}} 83 | */ 84 | protected function addToolbar() 85 | { 86 | $app = Factory::getApplication(); 87 | $app->getInput()->set('hidemainmenu', true); 88 | 89 | $user = $app->getIdentity(); 90 | $userId = $user->get('id'); 91 | $isNew = (int) $this->item->id === 0; 92 | $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId); 93 | 94 | // Since we don't track these assets at the item level, use the category id. 95 | $canDo = {{component_capitalize}}Helper::getActions('{{prefix_component}}'); 96 | 97 | ToolbarHelper::title($isNew ? Text::_('{{prefix_component_uppercase}}_NOTE_NEW') : Text::_('{{prefix_component_uppercase}}_NOTE_EDIT'), 'book edit-note'); 98 | 99 | // Build the actions for new and existing records. 100 | if ($isNew) 101 | { 102 | // For new records, check the create permission. 103 | if ($isNew) 104 | { 105 | ToolbarHelper::apply('note.apply'); 106 | 107 | ToolbarHelper::saveGroup( 108 | [ 109 | ['save', 'note.save'], 110 | ['save2new', 'note.save2new'] 111 | ], 112 | 'btn-success' 113 | ); 114 | } 115 | 116 | ToolbarHelper::cancel('note.cancel'); 117 | } 118 | else 119 | { 120 | // Since it's an existing record, check the edit permission, or fall back to edit own if the owner. 121 | $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by === $userId); 122 | 123 | $toolbarButtons = []; 124 | 125 | // Can't save the record if it's checked out and editable 126 | if (!$checkedOut && $itemEditable) 127 | { 128 | ToolbarHelper::apply('note.apply'); 129 | 130 | $toolbarButtons[] = ['save', 'note.save']; 131 | 132 | // We can save this record, but check the create permission to see if we can return to make a new one. 133 | if ($canDo->get('core.create')) 134 | { 135 | $toolbarButtons[] = ['save2new', 'note.save2new']; 136 | } 137 | } 138 | 139 | // If checked out, we can still save 140 | if ($canDo->get('core.create')) 141 | { 142 | $toolbarButtons[] = ['save2copy', 'note.save2copy']; 143 | } 144 | 145 | ToolbarHelper::saveGroup( 146 | $toolbarButtons, 147 | 'btn-success' 148 | ); 149 | 150 | ToolbarHelper::cancel('note.cancel', 'JTOOLBAR_CLOSE'); 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/src/View/Notes/HtmlView.jext: -------------------------------------------------------------------------------- 1 | items = $this->get('Items'); 83 | $this->pagination = $this->get('Pagination'); 84 | $this->state = $this->get('State'); 85 | $this->filterForm = $this->get('FilterForm'); 86 | $this->activeFilters = $this->get('ActiveFilters'); 87 | 88 | $errors = $this->get('Errors'); 89 | 90 | // Check for errors. 91 | if (count($errors = $this->get('Errors'))) 92 | { 93 | throw new GenericDataException(implode("\n", $errors), 500); 94 | } 95 | 96 | $this->addToolbar(); 97 | 98 | return parent::display($tpl); 99 | } 100 | 101 | /** 102 | * Add the page title and toolbar. 103 | * 104 | * @return void 105 | * 106 | * @since {{version}} 107 | */ 108 | protected function addToolbar() 109 | { 110 | $canDo = {{component_capitalize}}Helper::getActions('{{prefix_component}}'); 111 | $user = Factory::getApplication()->getIdentity(); 112 | 113 | // Get the toolbar object instance 114 | $toolbar = Toolbar::getInstance('toolbar'); 115 | 116 | ToolbarHelper::title(Text::_('{{prefix_component_uppercase}}_NOTES_TOOLBAR_LABEL'), 'book'); 117 | 118 | if ($canDo->get('core.create')) 119 | { 120 | $toolbar->addNew('note.add'); 121 | } 122 | 123 | if ($canDo->get('core.edit.state')) 124 | { 125 | $dropdown = $toolbar->dropdownButton('status-group') 126 | ->text('JTOOLBAR_CHANGE_STATUS') 127 | ->toggleSplit(false) 128 | ->icon('icon-ellipsis-h') 129 | ->buttonClass('btn btn-action') 130 | ->listCheck(true); 131 | 132 | $childBar = $dropdown->getChildToolbar(); 133 | 134 | $childBar->publish('notes.publish')->listCheck(true); 135 | 136 | $childBar->unpublish('notes.unpublish')->listCheck(true); 137 | 138 | $childBar->archive('notes.archive')->listCheck(true); 139 | 140 | if ($user->authorise('core.admin')) 141 | { 142 | $childBar->checkin('notes.checkin')->listCheck(true); 143 | } 144 | 145 | if ((int) $this->state->get('filter.published') !== -2) 146 | { 147 | $childBar->trash('notes.trash')->listCheck(true); 148 | } 149 | } 150 | 151 | if ((int) $this->state->get('filter.published') === -2 && $canDo->get('core.delete')) 152 | { 153 | $toolbar->delete('notes.delete') 154 | ->text('JTOOLBAR_EMPTY_TRASH') 155 | ->message('JGLOBAL_CONFIRM_DELETE') 156 | ->listCheck(true); 157 | } 158 | 159 | if ($user->authorise('core.admin', '{{prefix_component}}') || $user->authorise('core.options', '{{prefix_component}}')) 160 | { 161 | $toolbar->preferences('{{prefix_component}}'); 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /src/Assets/component/administrator/tmpl/icomoon/default.jext: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager(); 17 | $wa->useScript('{{prefix_component}}.icons'); 18 | $wa->useStyle('{{prefix_component}}.icons'); 19 | 20 | $this->document->addScriptOptions('config', ['base' => rtrim(Uri::root(), '/')]); 21 | 22 | ?> 23 | 24 |
25 | 30 |
31 |

32 |
33 |
34 |
35 |
36 | 37 | {{credit}} 38 |
-------------------------------------------------------------------------------- /src/Assets/component/administrator/tmpl/note/edit.jext: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager(); 18 | $wa->useScript('keepalive') 19 | ->useScript('form.validate'); 20 | 21 | $app = Factory::getApplication(); 22 | $input = $app->getInput(); 23 | 24 | $this->useCoreUI = true; 25 | 26 | // In case of modal 27 | $isModal = $input->get('layout') === 'modal'; 28 | $layout = $isModal ? 'modal' : 'edit'; 29 | $tmpl = $isModal || $input->get('tmpl', '', 'cmd') === 'component' ? '&tmpl=component' : ''; 30 | ?> 31 | 32 |
33 | 34 | 35 | 36 |
37 | 'details')); ?> 38 | 39 | 40 |
41 |
42 |
43 |
44 | form->renderField('description'); ?> 45 |
46 |
47 |
48 |
49 |
50 |
51 | set('fields', 52 | array( 53 | 'published', 54 | 'created_by', 55 | 'created', 56 | 'access', 57 | 'language' 58 | ) 59 | ); ?> 60 | 61 | set('fields', null); ?> 62 |
63 |
64 |
65 |
66 | 67 | 68 |
69 | 70 | {{credit}} 71 | 72 | 73 | 74 | 75 |
76 | -------------------------------------------------------------------------------- /src/Assets/component/site/src/Controller/DisplayController.jext: -------------------------------------------------------------------------------- 1 | input->set('view', $this->input->get('view', 'notes')); 46 | 47 | parent::display($cachable, $urlparams); 48 | 49 | return $this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Assets/component/site/src/Controller/NotesController.jext: -------------------------------------------------------------------------------- 1 | app->getLanguage()->load('{{prefix_component}}', JPATH_ADMINISTRATOR); 58 | } 59 | 60 | /** 61 | * Dispatch a controller task. Redirecting the user if appropriate. 62 | * 63 | * @return void 64 | * 65 | * @since {{version}} 66 | */ 67 | public function checkAccess() 68 | { 69 | parent::checkAccess(); 70 | 71 | /** 72 | * Write down your own business logic for checking access. 73 | * In the case of access denial throw NotAllowed|Exception. 74 | * 75 | * @see com_config as reference. 76 | */ 77 | } 78 | 79 | /** 80 | * Get a controller from the component 81 | * 82 | * @param string $name Controller name 83 | * @param string $client Optional client (like Administrator, Site etc.) 84 | * @param array $config Optional controller config 85 | * 86 | * @return \Joomla\CMS\MVC\Controller\BaseController 87 | * 88 | * @since 1.0.0 89 | */ 90 | public function getController(string $name, string $client = '', array $config = array()): BaseController 91 | { 92 | /** 93 | * Write down your logic here if your want to change the location of your controllers 94 | * to the Administrator component. 95 | * 96 | * @see com_menus as reference. 97 | */ 98 | 99 | return parent::getController($name, $client, $config); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Assets/component/site/src/Helper/RouteHelper.jext: -------------------------------------------------------------------------------- 1 | input->getInt('id'); 48 | $this->setState('note.id', $pk); 49 | 50 | $offset = $app->input->getUInt('limitstart'); 51 | $this->setState('list.offset', $offset); 52 | 53 | // Load the parameters. 54 | $params = $app->getParams(); 55 | $this->setState('params', $params); 56 | 57 | $this->setState('filter.language', Multilanguage::isEnabled()); 58 | } 59 | 60 | /** 61 | * Method to get note data. 62 | * 63 | * @param integer $pk The id of the note. 64 | * 65 | * @return object|boolean Menu item data object on success, boolean false 66 | */ 67 | public function getItem($pk = null) 68 | { 69 | $user = Factory::getApplication()->getIdentity(); 70 | 71 | $pk = (int) ($pk ?: $this->getState('note.id')); 72 | 73 | if ($this->_item === null) 74 | { 75 | $this->_item = array(); 76 | } 77 | 78 | if (!isset($this->_item[$pk])) 79 | { 80 | try 81 | { 82 | $container = Factory::getContainer(); 83 | $app = Factory::getApplication(); 84 | $db = $container->get('DatabaseDriver'); 85 | $query = $db->getQuery(true); 86 | $user = $app->getIdentity(); 87 | 88 | $query->select( 89 | $db->quoteName( 90 | explode( 91 | ', ', 92 | $this->getState( 93 | 'list.select', 94 | 'a.id, a.title, a.alias, a.description, a.published, a.access, a.created, a.created_by, a.ordering, a.language, ' . 95 | 'a.checked_out, a.checked_out_time' 96 | ) 97 | ) 98 | ) 99 | ); 100 | 101 | $query->from($db->quoteName('#__{{component}}_notes', 'a')); 102 | 103 | $query->select($db->quoteName('l.title', 'language_title')) 104 | ->select($db->quoteName('l.image', 'language_image')) 105 | ->join( 106 | 'LEFT', 107 | $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language') 108 | ); 109 | 110 | // Join over the users for the checked out user. 111 | $query->select($db->quoteName('uc.name', 'editor')) 112 | ->join( 113 | 'LEFT', 114 | $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out') 115 | ); 116 | 117 | // Join over the asset groups. 118 | $query->select($db->quoteName('ag.title', 'access_level')) 119 | ->join( 120 | 'LEFT', 121 | $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access') 122 | ); 123 | 124 | $query->where($db->quoteName('a.id') . ' = :pk') 125 | ->bind(':pk', $pk, ParameterType::INTEGER); 126 | 127 | // Filter by access level. 128 | if ($access = $this->getState('filter.access')) 129 | { 130 | $query->where($db->quoteName('a.access') . ' = :access'); 131 | $query->bind(':access', $access, ParameterType::INTEGER); 132 | } 133 | 134 | $query->select($db->quoteName('ua.name', 'author_name')) 135 | ->join( 136 | 'LEFT', 137 | $db->quoteName('#__users', 'ua') . ' ON ' . $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by') 138 | ); 139 | 140 | // Filter by published state 141 | $published = (string) $this->getState('filter.published'); 142 | 143 | if (is_numeric($published)) 144 | { 145 | $query->where($db->quoteName('a.published') . ' = :published'); 146 | $query->bind(':published', $published, ParameterType::INTEGER); 147 | } 148 | elseif ($published === '') 149 | { 150 | $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)'); 151 | } 152 | 153 | // Filter on the language. 154 | if ($language = $this->getState('filter.language') && Multilanguage::isEnabled()) 155 | { 156 | $query->where($db->quoteName('a.language') . ' = :language'); 157 | $query->bind(':language', $language); 158 | } 159 | 160 | $db->setQuery($query); 161 | $data = $db->loadObject(); 162 | 163 | if (empty($data)) 164 | { 165 | throw new \Exception(''); 166 | } 167 | 168 | $this->_item[$pk] = $data; 169 | } 170 | catch (\Exception $e) 171 | { 172 | if ($e->getCode() == 404) 173 | { 174 | // Need to go through the error handler to allow Redirect to work. 175 | throw $e; 176 | } 177 | else 178 | { 179 | $this->setError($e); 180 | $this->_item[$pk] = false; 181 | } 182 | } 183 | } 184 | 185 | return $this->_item[$pk]; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Assets/component/site/src/Service/Router.jext: -------------------------------------------------------------------------------- 1 | db = Factory::getContainer()->get('DatabaseDriver'); 69 | $this->queryBuilder = $this->db->getQuery(true); 70 | 71 | $params = ComponentHelper::getParams('{{prefix_component}}'); 72 | $this->noIDs = (bool) $params->get('sef_ids', 1); 73 | 74 | $notes = new RouterViewConfiguration('notes'); 75 | $this->registerView($notes); 76 | $note = new RouterViewConfiguration('note'); 77 | $note->setKey('id')->setParent($notes); 78 | $this->registerView($note); 79 | 80 | /** Register your other other views here */ 81 | 82 | //{{inject: register_router_view}} 83 | 84 | parent::__construct($app, $menu); 85 | 86 | $this->attachRule(new MenuRules($this)); 87 | $this->attachRule(new StandardRules($this)); 88 | $this->attachRule(new NomenuRules($this)); 89 | } 90 | 91 | /** 92 | * Get missing alias from the provided ID. 93 | * 94 | * @param string $id The ID with or without the alias. 95 | * @param string $table The table name. 96 | * 97 | * @return string The alias string. 98 | * 99 | * @since {{version}} 100 | */ 101 | private function getAlias(string $id, string $table) : string 102 | { 103 | try 104 | { 105 | $this->queryBuilder->clear(); 106 | $this->queryBuilder->select('alias') 107 | ->from($this->db->quoteName($table)) 108 | ->where($this->db->quoteName('id') . ' = ' . (int) $id); 109 | $this->db->setQuery($this->queryBuilder); 110 | 111 | return (string) $this->db->loadResult(); 112 | } 113 | catch (\Exception $e) 114 | { 115 | echo $e->getMessage(); 116 | 117 | return ''; 118 | } 119 | } 120 | 121 | /** 122 | * Get id from the alias. 123 | * 124 | * @param string $alias The alias string. 125 | * @param string $table The table name. 126 | * 127 | * @return int The id. 128 | * 129 | * @since {{version}} 130 | */ 131 | private function getId(string $alias, string $table) : int 132 | { 133 | try 134 | { 135 | $this->queryBuilder->clear(); 136 | $this->queryBuilder->select('id') 137 | ->from($this->db->quoteName($table)) 138 | ->where($this->db->quoteName('alias') . ' = ' . $this->db->quote($alias)); 139 | $this->db->setQuery($this->queryBuilder); 140 | 141 | return (int) $this->db->loadResult(); 142 | } 143 | catch (\Exception $e) 144 | { 145 | echo $e->getMessage(); 146 | 147 | return 0; 148 | } 149 | } 150 | 151 | /** 152 | * Get the view segment for the common views. 153 | * 154 | * @param string $id The ID with or without alias. 155 | * @param string $table The table name. 156 | * 157 | * @return array The segment array. 158 | * 159 | * @since {{version}} 160 | */ 161 | private function getViewSegment(string $id, string $table) : array 162 | { 163 | if (strpos($id, ':') === false) 164 | { 165 | $id .= ':' . $this->getAlias($id, $table); 166 | } 167 | 168 | if ($this->noIDs) 169 | { 170 | list ($key, $alias) = explode(':', $id, 2); 171 | 172 | return [$key => $alias]; 173 | } 174 | 175 | return [(int) $id => $id]; 176 | } 177 | 178 | /** 179 | * get the view ID for the common pattern view. 180 | * 181 | * @param string $segment The segment string. 182 | * @param string $table The table name. 183 | * 184 | * @return int The id. 185 | * 186 | * @since {{version}} 187 | */ 188 | private function getViewId(string $segment, string $table) : int 189 | { 190 | return $this->noIDs 191 | ? $this->getId($segment, $table) 192 | : (int) $segment; 193 | } 194 | 195 | /** 196 | * Method to get the segment(s) for a note 197 | * 198 | * @param string $id ID of the category to retrieve the segments for 199 | * @param array $query The request that is built right now 200 | * 201 | * @return array|string The segments of this item 202 | * 203 | * @since {{version}} 204 | */ 205 | public function getNoteSegment($id, $query) 206 | { 207 | return $this->getViewSegment($id, '#__{{component}}_notes'); 208 | } 209 | 210 | /** 211 | * Method to get the note Id 212 | * 213 | * @param string $segment Segment of the note to retrieve the ID for 214 | * @param array $query The request that is parsed right now 215 | * 216 | * @return mixed The id of this item or false 217 | * 218 | * @since {{version}} 219 | */ 220 | public function getNoteId($segment, $query) 221 | { 222 | return $this->getViewId($segment, '#__{{component}}_notes'); 223 | } 224 | 225 | //{{inject: router_methods}} 226 | } 227 | -------------------------------------------------------------------------------- /src/Assets/component/site/src/View/Note/HtmlView.jext: -------------------------------------------------------------------------------- 1 | getIdentity(); 82 | 83 | $state = $this->get('State'); 84 | $item = $this->get('Item'); 85 | $model = $this->getModel(); 86 | 87 | // Check for errors. 88 | if (count($errors = $this->get('Errors'))) 89 | { 90 | throw new GenericDataException(implode("\n", $errors), 500); 91 | } 92 | 93 | $params = $state->get('params'); 94 | 95 | // Escape strings for HTML output 96 | $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx')); 97 | 98 | $this->params = $params; 99 | $this->state = $state; 100 | $this->item = $item; 101 | $this->user = $user; 102 | 103 | return parent::display($tpl); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Assets/component/site/src/View/Notes/HtmlView.jext: -------------------------------------------------------------------------------- 1 | getIdentity(); 83 | 84 | $state = $this->get('State'); 85 | $items = $this->get('Items'); 86 | $pagination = $this->get('Pagination'); 87 | 88 | // Flag indicates to not add limitstart=0 to URL 89 | $pagination->hideEmptyLimitstart = true; 90 | 91 | // Check for errors. 92 | if (count($errors = $this->get('Errors'))) 93 | { 94 | throw new GenericDataException(implode("\n", $errors), 500); 95 | } 96 | 97 | /** @var \Joomla\Registry\Registry $params */ 98 | $params = &$state->params; 99 | 100 | foreach ($items as &$item) 101 | { 102 | $item->url = RouteHelper::generateUrl('{{singular}}', $item->id, $item->alias, $item->language); 103 | } 104 | 105 | $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx')); 106 | 107 | $this->params = $params; 108 | $this->items = $items; 109 | $this->pagination = $pagination; 110 | $this->user = $user; 111 | 112 | parent::display($tpl); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/Assets/component/site/tmpl/note/default.jext: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager(); 13 | $wa->useStyle('{{prefix_component}}.notes'); 14 | 15 | ?> 16 | 17 |
18 |

item->title; ?>

19 |
item->description; ?>
20 | 21 | {{credit}} 22 |
-------------------------------------------------------------------------------- /src/Assets/component/site/tmpl/notes/default.jext: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager(); 13 | $wa->useStyle('{{prefix_component}}.notes'); 14 | 15 | ?> 16 | 17 |
18 |
19 | items as $item): ?> 20 |
21 |

title; ?>

22 | description); ?> 23 |
200 ? substr($stripedDescription, 0, 200) . '...' : $stripedDescription; ?>
24 | 25 |
26 | 27 |
28 | pagination->getListFooter(); ?> 29 | 30 | {{credit}} 31 |
-------------------------------------------------------------------------------- /src/Assets/component/site/tmpl/notes/menu.jext: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/Assets/injection/administrator/install.sql.jext: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `#__{{component}}_{{plural}}` ( 2 | `id` bigint NOT NULL AUTO_INCREMENT, 3 | `asset_id` bigint NOT NULL DEFAULT 0, 4 | `title` varchar(255) NOT NULL DEFAULT '', 5 | `alias` varchar(300) NOT NULL DEFAULT '', 6 | `published` tinyint(1) NOT NULL DEFAULT 0, 7 | `checked_out` int unsigned NOT NULL DEFAULT 0, 8 | `checked_out_time` datetime, 9 | `ordering` int NOT NULL DEFAULT 0, 10 | `access` int unsigned NOT NULL DEFAULT 0, 11 | `language` varchar(7) NOT NULL DEFAULT '*', 12 | `created` datetime NOT NULL, 13 | `created_by` int unsigned NOT NULL DEFAULT 0, 14 | `modified` datetime NOT NULL, 15 | `modified_by` int unsigned NOT NULL DEFAULT 0, 16 | PRIMARY KEY (`id`) 17 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; 18 | 19 | --{{inject: install_table}} -------------------------------------------------------------------------------- /src/Assets/injection/administrator/language.jext: -------------------------------------------------------------------------------- 1 | ;; {{plural_capitalize}} view 2 | {{prefix_component_uppercase}}_{{plural_uppercase}}_TOOLBAR_LABEL="{{plural_capitalize}}" 3 | 4 | ;; {{singular_capitalize}} view (single) 5 | {{prefix_component_uppercase}}_{{singular_uppercase}}_NEW="Add new {{singular}}" 6 | {{prefix_component_uppercase}}_{{singular_uppercase}}_EDIT="Edit {{singular}}" 7 | 8 | ;;{{inject: administrator_language}} -------------------------------------------------------------------------------- /src/Assets/injection/administrator/language.sys.jext: -------------------------------------------------------------------------------- 1 | {{prefix_component_uppercase}}_SUBMENU_{{plural_uppercase}}="{{plural_capitalize}}" 2 | 3 | ;;{{inject: administrator_system_language}} -------------------------------------------------------------------------------- /src/Assets/injection/administrator/submenu.jext: -------------------------------------------------------------------------------- 1 | {{prefix_component_uppercase}}_SUBMENU_{{plural_uppercase}} 2 | -------------------------------------------------------------------------------- /src/Assets/injection/media/asset.jext: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{prefix_component}}.icons", 3 | "type": "script", 4 | "uri": "{{prefix_component}}/icons.js", 5 | "dependencies": ["core"], 6 | "attributes": { 7 | "defer": true 8 | } 9 | }, 10 | { 11 | "name": "{{prefix_component}}.icons", 12 | "type": "style", 13 | "uri": "{{prefix_component}}/icons.css" 14 | }, 15 | { 16 | "name": "{{prefix_component}}.notes", 17 | "type": "style", 18 | "uri": "{{prefix_component}}/notes.css" 19 | } -------------------------------------------------------------------------------- /src/Assets/injection/site/menuitem.language.jext: -------------------------------------------------------------------------------- 1 | {{prefix_component_uppercase}}_{{plural_uppercase}}_VIEW_DEFAULT_TITLE="{{plural_capitalize}}" 2 | {{prefix_component_uppercase}}_{{plural_uppercase}}_VIEW_DEFAULT_OPTION="Default" 3 | JHELP_MENUS_MENU_ITEM_{{plural_uppercase}}="Menu_Item:_{{plural_capitalize}}" 4 | {{prefix_component_uppercase}}_{{plural_uppercase}}_VIEW_DEFAULT_DESC="Show all the {{plural}}." 5 | {{prefix_component_uppercase}}_{{plural_uppercase}}_SETTINGS_LABEL="Settings" 6 | 7 | ;;{{inject: site_menuitem_language}} -------------------------------------------------------------------------------- /src/Assets/injection/site/router.jext: -------------------------------------------------------------------------------- 1 | 2 | ${{plural}} = new RouterViewConfiguration('{{plural}}'); 3 | $this->registerView(${{plural}}); 4 | ${{singular}} = new RouterViewConfiguration('{{singular}}'); 5 | ${{singular}}->setKey('id')->setParent(${{plural}}); 6 | $this->registerView(${{singular}}); 7 | 8 | //{{inject: register_router_view}} -------------------------------------------------------------------------------- /src/Assets/injection/site/router.methods.jext: -------------------------------------------------------------------------------- 1 | /** 2 | * Method to get the segment(s) for a {{singular}} 3 | * 4 | * @param string $id ID of the category to retrieve the segments for 5 | * @param array $query The request that is built right now 6 | * 7 | * @return array|string The segments of this item 8 | * 9 | * @since {{version}} 10 | */ 11 | public function get{{singular_capitalize}}Segment($id, $query) 12 | { 13 | return $this->getViewSegment($id, '#__{{component}}_{{plural}}'); 14 | } 15 | 16 | /** 17 | * Method to get the {{singular}} Id 18 | * 19 | * @param string $segment Segment of the {{singular}} to retrieve the ID for 20 | * @param array $query The request that is parsed right now 21 | * 22 | * @return mixed The id of this item or false 23 | * 24 | * @since {{version}} 25 | */ 26 | public function get{{singular_capitalize}}Id($segment, $query) 27 | { 28 | return $this->getViewId($segment, '#__{{component}}_{{plural}}'); 29 | } 30 | 31 | //{{inject: router_methods}} -------------------------------------------------------------------------------- /src/Assets/language/administrator/extension.jext: -------------------------------------------------------------------------------- 1 | ;; Basic Texts 2 | {{prefix_component_uppercase}}_ERROR_UNIQUE_ALIAS="Another item has the same alias (remember it may be a trashed item)." 3 | {{prefix_component_uppercase}}_GLOBAL_TITLE="Title" 4 | {{prefix_component_uppercase}}_GLOBAL_TITLE_DESC="Write down the title" 5 | {{prefix_component_uppercase}}_GLOBAL_ALIAS="Alias" 6 | {{prefix_component_uppercase}}_GLOBAL_ALIAS_DESC="Write down a unique alias value or leave it empty." 7 | {{prefix_component_uppercase}}_GLOBAL_SEARCH_LABEL="Search" 8 | {{prefix_component_uppercase}}_GLOBAL_SEARCH_LABEL_DESC="Enter search keywords here" 9 | {{prefix_component_uppercase}}_GLOBAL_TAB_BASIC="Basic" 10 | {{prefix_component_uppercase}}_N_ITEMS_PUBLISHED="%d item(s) published" 11 | {{prefix_component_uppercase}}_N_ITEMS_UNPUBLISHED="%d item(s) unpublished" 12 | {{prefix_component_uppercase}}_N_ITEMS_CHECKED_IN="%d item(s) checked in" 13 | {{prefix_component_uppercase}}_N_ITEMS_ARCHIVED="%d item(s) archived" 14 | {{prefix_component_uppercase}}_N_ITEMS_TRASHED="%d item(s) trashed" 15 | {{prefix_component_uppercase}}_N_ITEMS_DELETED="%d item(s) deleted" 16 | 17 | ;; Notes view 18 | {{prefix_component_uppercase}}_NOTES_TOOLBAR_LABEL="Notes" 19 | 20 | ;; Note view (single) 21 | {{prefix_component_uppercase}}_NOTE_NEW="Add new note" 22 | {{prefix_component_uppercase}}_NOTE_EDIT="Edit note" 23 | {{prefix_component_uppercase}}_NOTE_DESCRIPTION="Your Note" 24 | {{prefix_component_uppercase}}_NOTE_DESCRIPTION_DESC="Write down your note here." 25 | 26 | ;;{{inject: administrator_language}} -------------------------------------------------------------------------------- /src/Assets/language/administrator/extension.sys.jext: -------------------------------------------------------------------------------- 1 | {{prefix_component_uppercase}}="{{component_capitalize}}" 2 | {{prefix_component_uppercase}}_DASHBOARD_TITLE="Dashboard" 3 | {{prefix_component_uppercase}}_XML_DESCRIPTION="{{description}}" 4 | {{prefix_component_uppercase}}_NOTES="Notes" 5 | {{prefix_component_uppercase}}_ICOMOON="Icons" 6 | {{prefix_component_uppercase}}_CONFIGURATION="Configurations" 7 | 8 | ;; Menu Items 9 | {{prefix_component_uppercase}}_NOTES_VIEW_DEFAULT_TITLE="Notes" 10 | {{prefix_component_uppercase}}_NOTES_VIEW_DEFAULT_OPTION="Default" 11 | JHELP_MENUS_MENU_ITEM_NOTES="Menu_Item:_Notes" 12 | {{prefix_component_uppercase}}_NOTES_VIEW_DEFAULT_DESC="Show all the notes." 13 | {{prefix_component_uppercase}}_NOTES_SETTINGS_LABEL="Settings" 14 | 15 | ;; Settings 16 | {{prefix_component_uppercase}}_CONFIG_BASIC="Basic" 17 | {{prefix_component_uppercase}}_CONFIG_BASIC_DESC="Basic settings" 18 | 19 | ;;{{inject: site_menuitem_language}} 20 | 21 | ;;{{inject: administrator_system_language}} -------------------------------------------------------------------------------- /src/Assets/language/site/extension.jext: -------------------------------------------------------------------------------- 1 | {{prefix_component_uppercase}}="{{component_uppercase}}" -------------------------------------------------------------------------------- /src/Assets/media/css/icons.jext: -------------------------------------------------------------------------------- 1 | .{{component}}-icomoon-icons { 2 | display: flex; 3 | justify-content: flex-start; 4 | flex-wrap: wrap; 5 | } 6 | .{{component}}-icomoon-icons .box { 7 | width: 120px; 8 | height: 120px; 9 | margin-bottom: 10px; 10 | margin-right: 10px; 11 | cursor: pointer; 12 | color: #495057; 13 | text-decoration: none; 14 | position: relative; 15 | } 16 | 17 | .{{component}}-icomoon-icons .box .{{component}}-icon-preview { 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | height: 50%; 22 | } 23 | 24 | .{{component}}-icomoon-icons .box:hover { 25 | background: #fff; 26 | box-shadow: 0 0 2px 2px #dee2e694; 27 | border-radius: 5px; 28 | } 29 | 30 | .{{component}}-icomoon-icons .box:hover .{{component}}-icon-preview { 31 | background: #754ef9; 32 | color: #fff; 33 | border-top-left-radius: 5px; 34 | border-top-right-radius: 5px; 35 | } 36 | 37 | .{{component}}-icomoon-icons .box .{{component}}-icon-info { 38 | height: 50%; 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | text-align: center; 43 | padding: 0 10px; 44 | } 45 | 46 | .{{component}}-icomoon-icons .box .preview { 47 | font-size: 36px; 48 | } 49 | 50 | .{{component}}-icons-searchbar { 51 | margin-bottom: 40px; 52 | position: relative; 53 | } 54 | 55 | .{{component}}-icons-searchbar .placeholder-icon, 56 | .{{component}}-icons-searchbar .remove-icon { 57 | position: absolute; 58 | top: 20px; 59 | right: 20px; 60 | font-size: 20px; 61 | color: #ccc; 62 | } 63 | 64 | .{{component}}-icons-searchbar .remove-icon { 65 | display: none; 66 | cursor: pointer; 67 | transition: color 0.3s ease; 68 | } 69 | 70 | .{{component}}-icons-searchbar .remove-icon:hover { 71 | color: #222; 72 | } 73 | 74 | .{{component}}-icons-searchbar .{{component}}-icon-search { 75 | height: 60px; 76 | } 77 | 78 | .{{component}}-icon-info > span { 79 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 80 | font-weight: 400; 81 | font-size: 14px; 82 | } 83 | 84 | .{{component}}-copied-message { 85 | position: absolute; 86 | top: 0; 87 | left: 0; 88 | width: 100%; 89 | display: flex; 90 | justify-content: center; 91 | display: none; 92 | color: #fff; 93 | background: rgba(0, 0, 0, 0.6); 94 | border-radius: 5px; 95 | } 96 | 97 | .{{component}}-no-icon { 98 | display: flex; 99 | flex-direction: column; 100 | justify-content: center; 101 | align-items: center; 102 | width: 100%; 103 | } 104 | 105 | .{{component}}-no-icon h2 { 106 | font-family: monospace; 107 | margin-top: 20px; 108 | } 109 | 110 | .{{component}}-message-bar { 111 | border-bottom: 2px solid rgb(187, 171, 171, 0.21); 112 | margin-bottom: 20px; 113 | padding-bottom: 10px; 114 | display: flex; 115 | align-items: center; 116 | justify-content: space-between; 117 | } 118 | 119 | .{{component}}-search-keyword { 120 | background: rgba(0, 0, 0, 0.6); 121 | border-radius: 30px; 122 | padding: 0px 10px; 123 | color: #fff; 124 | display: inline-flex; 125 | justify-content: space-between; 126 | align-items: center; 127 | min-width: 80px; 128 | cursor: pointer; 129 | transition: all 0.3s ease; 130 | } 131 | 132 | .{{component}}-search-keyword:hover { 133 | background: rgba(255, 0, 0, 0.8); 134 | } 135 | -------------------------------------------------------------------------------- /src/Assets/media/css/notes.jext: -------------------------------------------------------------------------------- 1 | body { 2 | background: aliceblue !important; 3 | } 4 | 5 | .{{component}}-notes { 6 | max-width: 800px; 7 | margin-left: auto; 8 | margin-right: auto; 9 | font-size: 14px; 10 | } 11 | 12 | .{{component}}-notes .{{component}}-notes-item .{{component}}-note-title { 13 | font-size: 20px; 14 | margin-bottom: 20px; 15 | border-bottom: 1px solid #f1f1f1; 16 | padding-bottom: 10px; 17 | } 18 | 19 | .{{component}}-notes .{{component}}-notes-item .{{component}}-note-link { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | bottom: 0; 24 | left: 0; 25 | } 26 | 27 | .{{component}}-notes .{{component}}-notes-item { 28 | position: relative; 29 | display: flex; 30 | flex-direction: column; 31 | width: 100%; 32 | min-height: 160px; 33 | background: #fff; 34 | margin-bottom: 20px; 35 | border-radius: 10px; 36 | padding: 20px; 37 | box-shadow: 1px 1px 1px 0 #22222247; 38 | cursor: pointer; 39 | transition: all 0.3s ease; 40 | } 41 | 42 | .{{component}}-notes .{{component}}-notes-item:hover { 43 | box-shadow: 1px 1px 5px 2px #22222247; 44 | } 45 | -------------------------------------------------------------------------------- /src/Assets/media/css/styles.jext: -------------------------------------------------------------------------------- 1 | /** Put your styles here */ -------------------------------------------------------------------------------- /src/Assets/media/joomla.asset.jext: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json", 3 | "name": "{{prefix_component}}", 4 | "version": "{{version}}", 5 | "description": "{{description}}", 6 | "license": "{{license}}", 7 | "assets": [ 8 | { 9 | "name": "{{prefix_component}}.styles", 10 | "type": "style", 11 | "uri": "{{prefix_component}}/styles.css" 12 | }, 13 | //{{inject: components_view_assets}} 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/Assets/media/js/icons.jext: -------------------------------------------------------------------------------- 1 | const config = Joomla.getOptions('config') || {}; 2 | const prefix = '{{component}}'; 3 | 4 | window.addEventListener('DOMContentLoaded', async () => { 5 | async function init() { 6 | await getIcons(); 7 | handleIconSearch(); 8 | } 9 | 10 | init(); 11 | 12 | /** 13 | * Handling the icon search 14 | */ 15 | function handleIconSearch() { 16 | const element = document.querySelector(`input.${prefix}-icon-search`); 17 | const placeholder = document.querySelector( 18 | `.${prefix}-icons-searchbar .placeholder-icon` 19 | ); 20 | const remove = document.querySelector( 21 | `.${prefix}-icons-searchbar .remove-icon` 22 | ); 23 | 24 | let timeout = null; 25 | 26 | /** Filter the icons */ 27 | element.addEventListener('keyup', async event => { 28 | event.preventDefault(); 29 | 30 | const { value } = event.target; 31 | timeout && clearTimeout(timeout); 32 | 33 | if (value !== '') { 34 | remove.style.display = 'inline-block'; 35 | placeholder.style.display = 'none'; 36 | } else { 37 | remove.style.display = 'none'; 38 | placeholder.style.display = 'inline-block'; 39 | } 40 | 41 | timeout = setTimeout(async () => { 42 | await getIcons(value); 43 | }, 300); 44 | }); 45 | 46 | /** Remove the filter */ 47 | remove.addEventListener('click', async e => { 48 | e.preventDefault(); 49 | element.value = ''; 50 | await getIcons(); 51 | }); 52 | } 53 | 54 | /** 55 | * Get the icons using API request. 56 | * 57 | * @param {string} keyword 58 | */ 59 | async function getIcons(keyword = '') { 60 | const element = document.querySelector(`.${prefix}-icomoon-icons`); 61 | const totalMessage = document.querySelector(`.${prefix}-total-icons`); 62 | const keywordElement = document.querySelector( 63 | `.${prefix}-search-tokens` 64 | ); 65 | 66 | const url = `${config.base}/administrator/index.php?option=com_${prefix}&task=icomoon.getIcons&keyword=${keyword}`; 67 | 68 | const res = await fetch(url, { 69 | method: 'GET', 70 | headers: { 71 | 'Content-Type': 'application/json', 72 | }, 73 | }); 74 | 75 | const data = await res.json(); 76 | 77 | if (data.success) { 78 | element.innerHTML = data.data.html; 79 | totalMessage.innerHTML = `Total ${data.data.total} ${ 80 | data.data.total <= 0 ? 'Icon' : 'Icons' 81 | }`; 82 | 83 | if (keyword.length > 0) { 84 | keyword = keyword.replace(/\s+/g, ' ').trim(); 85 | const keywordArray = keyword.split(/\s+/); 86 | 87 | keywordElement.innerHTML = keywordArray 88 | .map( 89 | (token, index) => 90 | `
${token}
` 91 | ) 92 | .join('\n'); 93 | 94 | removeKeyword(); 95 | } else { 96 | keywordElement.innerHTML = ''; 97 | } 98 | 99 | copy2clipboard(); 100 | } 101 | } 102 | 103 | /** 104 | * Remove search keyword tokens on click 105 | */ 106 | function removeKeyword() { 107 | const items = document.querySelectorAll(`.${prefix}-search-keyword`); 108 | const element = document.querySelector(`input.${prefix}-icon-search`); 109 | let keyword = element.value; 110 | 111 | keyword = keyword.replace(/\s+/, ' ').trim(); 112 | const keywordArray = keyword.split(' '); 113 | 114 | for (const item of items) { 115 | item.addEventListener('click', async e => { 116 | const index = item.dataset.index; 117 | 118 | keywordArray.splice(index, 1); 119 | element.value = keywordArray.join(' '); 120 | 121 | await getIcons(element.value); 122 | }); 123 | } 124 | } 125 | 126 | /** 127 | * Copy icon name to the clipboard with icon- prefix 128 | */ 129 | function copy2clipboard() { 130 | const elements = document.querySelectorAll( 131 | `.${prefix}-icomoon-icons .box` 132 | ); 133 | 134 | for (const element of elements) { 135 | element.addEventListener('click', e => { 136 | e.preventDefault(); 137 | 138 | const iconName = element.querySelector('.icon-name'); 139 | const messageEl = element.querySelector( 140 | `.${prefix}-copied-message` 141 | ); 142 | const virtualEl = document.createElement('input'); 143 | virtualEl.value = iconName.innerHTML; 144 | document.body.appendChild(virtualEl); 145 | virtualEl.select(); 146 | 147 | try { 148 | const response = document.execCommand('copy'); 149 | 150 | if (response) { 151 | messageEl.style.display = 'flex'; 152 | setTimeout(() => { 153 | messageEl.style.display = 'none'; 154 | }, 1000); 155 | } 156 | } catch (e) { 157 | console.error(e.message); 158 | } 159 | 160 | document.body.removeChild(virtualEl); 161 | }); 162 | } 163 | } 164 | }); 165 | -------------------------------------------------------------------------------- /src/Assets/view/administrator/forms/filter.jext: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 12 | 13 | 20 | 21 | 22 | 23 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | 81 |
-------------------------------------------------------------------------------- /src/Assets/view/administrator/forms/form.jext: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 12 | 13 | 18 | 19 | 27 | 28 | 36 | 37 | 38 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 64 | 65 | 71 | 72 | 81 | 82 | 91 | 92 | 103 | 104 | 111 | 112 | 117 | 118 | 119 |
120 | Built 121 |
122 | -------------------------------------------------------------------------------- /src/Assets/view/administrator/src/Controller/plural.jext: -------------------------------------------------------------------------------- 1 | true)) 53 | { 54 | return parent::getModel($name, $prefix, $config); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Assets/view/administrator/src/Controller/singular.jext: -------------------------------------------------------------------------------- 1 | app->getIdentity(); 72 | 73 | return $user->authorise('core.create', '{{prefix_component}}.{{singular}}'); 74 | } 75 | 76 | /** 77 | * Method override to check if you can edit an existing record. 78 | * 79 | * @param array $data An array of input data. 80 | * @param string $key The name of the key for the primary key. 81 | * 82 | * @return boolean 83 | * 84 | * @since 1.0.0 85 | */ 86 | protected function allowEdit($data = array(), $key = 'id') 87 | { 88 | $recordId = (int) isset($data[$key]) ? $data[$key] : 0; 89 | $user = $this->app->getIdentity(); 90 | 91 | // Zero record (id:0), return component edit permission by calling parent controller method 92 | if (!$recordId) 93 | { 94 | return parent::allowEdit($data, $key); 95 | } 96 | 97 | // Check edit on the record asset (explicit or inherited) 98 | if ($user->authorise('core.edit', '{{prefix_component}}.{{singular}}.' . $recordId)) 99 | { 100 | return true; 101 | } 102 | 103 | // Check edit own on the record asset (explicit or inherited) 104 | if ($user->authorise('core.edit.own', '{{prefix_component}}.{{singular}}.' . $recordId)) 105 | { 106 | // Existing record already has an owner, get it 107 | $record = $this->getModel()->getItem($recordId); 108 | 109 | if (empty($record)) 110 | { 111 | return false; 112 | } 113 | 114 | // Grant if current user is owner of the record 115 | return $user->id === $record->created_by; 116 | } 117 | 118 | return false; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Assets/view/administrator/src/Model/plural.jext: -------------------------------------------------------------------------------- 1 | getInput()->get('forcedLanguage', '', 'cmd'); 73 | 74 | // Adjust the context to support modal layouts. 75 | if ($layout = $app->input->get('layout')) 76 | { 77 | $this->context .= '.' . $layout; 78 | } 79 | 80 | // Adjust the context to support forced languages. 81 | if ($forcedLanguage) 82 | { 83 | $this->context .= '.' . $forcedLanguage; 84 | } 85 | 86 | // List state information. 87 | parent::populateState($ordering, $direction); 88 | 89 | // Force a language. 90 | if (!empty($forcedLanguage)) 91 | { 92 | $this->setState('filter.language', $forcedLanguage); 93 | } 94 | } 95 | 96 | /** 97 | * Method to get a store id based on model configuration state. 98 | * 99 | * This is necessary because the model is used by the component and 100 | * different modules that might need different sets of data or different 101 | * ordering requirements. 102 | * 103 | * @param string $id A prefix for the store id. 104 | * 105 | * @return string A store id. 106 | * 107 | * @since {{version}} 108 | */ 109 | protected function getStoreId($id = '') 110 | { 111 | // Compile the store id. 112 | $id .= ':' . $this->getState('filter.search'); 113 | $id .= ':' . $this->getState('filter.published'); 114 | $id .= ':' . $this->getState('filter.access'); 115 | $id .= ':' . $this->getState('filter.language'); 116 | $id .= ':' . serialize($this->getState('filter.tag')); 117 | $id .= ':' . $this->getState('filter.level'); 118 | 119 | return parent::getStoreId($id); 120 | } 121 | 122 | /** 123 | * Build an SQL query to load the list data. 124 | * 125 | * @return \JDatabaseQuery 126 | * 127 | * @since {{version}} 128 | */ 129 | protected function getListQuery() 130 | { 131 | $container = Factory::getContainer(); 132 | $app = Factory::getApplication(); 133 | $db = $container->get('DatabaseDriver'); 134 | $query = $db->getQuery(true); 135 | $user = $app->getIdentity(); 136 | 137 | $query->select( 138 | $db->quoteName( 139 | explode( 140 | ', ', 141 | $this->getState( 142 | 'list.select', 143 | 'a.id, a.title, a.alias, a.published, a.access, a.created, a.created_by, a.ordering, a.language, ' . 144 | 'a.checked_out, a.checked_out_time' 145 | ) 146 | ) 147 | ) 148 | ); 149 | 150 | $query->from($db->quoteName('#__{{component}}_{{plural}}', 'a')); 151 | 152 | $query->select($db->quoteName('l.title', 'language_title')) 153 | ->select($db->quoteName('l.image', 'language_image')) 154 | ->join( 155 | 'LEFT', 156 | $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language') 157 | ); 158 | 159 | // Join over the users for the checked out user. 160 | $query->select($db->quoteName('uc.name', 'editor')) 161 | ->join( 162 | 'LEFT', 163 | $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out') 164 | ); 165 | 166 | $query->select($db->quoteName('ua.name', 'author_name')) 167 | ->join( 168 | 'LEFT', 169 | $db->quoteName('#__users', 'ua') . ' ON ' . $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by') 170 | ); 171 | 172 | // Join over the asset groups. 173 | $query->select($db->quoteName('ag.title', 'access_level')) 174 | ->join( 175 | 'LEFT', 176 | $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access') 177 | ); 178 | 179 | // Filter by access level. 180 | if ($access = $this->getState('filter.access')) 181 | { 182 | $query->where($db->quoteName('a.access') . ' = :access'); 183 | $query->bind(':access', $access, ParameterType::INTEGER); 184 | } 185 | 186 | // Implement View Level Access 187 | if (!$user->authorise('core.admin')) 188 | { 189 | $query->whereIn($db->quoteName('a.access'), $user->getAuthorisedViewLevels()); 190 | } 191 | 192 | // Filter by published state 193 | $published = (string) $this->getState('filter.published'); 194 | 195 | if (is_numeric($published)) 196 | { 197 | $query->where($db->quoteName('a.published') . ' = :published'); 198 | $query->bind(':published', $published, ParameterType::INTEGER); 199 | } 200 | elseif ($published === '') 201 | { 202 | $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)'); 203 | } 204 | 205 | // Filter by search in name. 206 | $search = $this->getState('filter.search'); 207 | 208 | if (!empty($search)) 209 | { 210 | if (stripos($search, 'id:') === 0) 211 | { 212 | $search = substr($search, 3); 213 | $query->where($db->quoteName('a.id') . ' = :id'); 214 | $query->bind(':id', $search, ParameterType::INTEGER); 215 | } 216 | else 217 | { 218 | $search = '%' . trim($search) . '%'; 219 | $query->where( 220 | '(' . $db->quoteName('a.name') . ' LIKE :name OR ' . $db->quoteName('a.alias') . ' LIKE :alias)' 221 | ); 222 | $query->bind(':name', $search); 223 | $query->bind(':alias', $search); 224 | } 225 | } 226 | 227 | // Filter on the language. 228 | if ($language = $this->getState('filter.language')) 229 | { 230 | $query->where($db->quoteName('a.language') . ' = :language'); 231 | $query->bind(':language', $language); 232 | } 233 | 234 | // Add the list ordering clause. 235 | $orderCol = $this->state->get('list.ordering', 'a.title'); 236 | $orderDirn = $this->state->get('list.direction', 'asc'); 237 | 238 | if ($orderCol === 'a.ordering') 239 | { 240 | $orderCol = $db->quoteName('a.ordering'); 241 | } 242 | 243 | $query->order($db->escape($orderCol . ' ' . $orderDirn)); 244 | 245 | return $query; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Assets/view/administrator/src/Table/table.jext: -------------------------------------------------------------------------------- 1 | toSql(); 69 | $userId = Factory::getApplication()->getIdentity()->get('id'); 70 | 71 | // Set created date if not set. 72 | if (!(int) $this->created) 73 | { 74 | $this->created = $date; 75 | } 76 | 77 | if ($this->id) 78 | { 79 | // Existing item 80 | $this->modified_by = $userId; 81 | $this->modified = $date; 82 | } 83 | else 84 | { 85 | // Field created_by field can be set by the user, so we don't touch it if it's set. 86 | if (empty($this->created_by)) 87 | { 88 | $this->created_by = $userId; 89 | } 90 | 91 | if (!(int) $this->modified) 92 | { 93 | $this->modified = $date; 94 | } 95 | 96 | if (empty($this->modified_by)) 97 | { 98 | $this->modified_by = $userId; 99 | } 100 | } 101 | 102 | // Verify that the alias is unique 103 | $table = Table::getInstance('{{singular_capitalize}}Table', __NAMESPACE__ . '\\', array('dbo' => $this->getDbo())); 104 | $table->load(array('alias' => $this->alias)); 105 | 106 | if ($table->load(array('alias' => $this->alias)) && ($table->id !== $this->id || $this->id === 0)) 107 | { 108 | $this->setError(Text::_('{{prefix_component_uppercase}}_ERROR_UNIQUE_ALIAS')); 109 | 110 | return false; 111 | } 112 | 113 | return parent::store($updateNulls); 114 | } 115 | 116 | /** 117 | * Overloaded check function 118 | * 119 | * @return boolean True on success, false on failure 120 | * 121 | * @see \JTable::check 122 | * @since 1.5 123 | */ 124 | public function check() 125 | { 126 | try 127 | { 128 | parent::check(); 129 | } 130 | catch (\Exception $e) 131 | { 132 | $this->setError($e->getMessage()); 133 | 134 | return false; 135 | } 136 | 137 | // Generate a valid alias 138 | $this->generateAlias(); 139 | 140 | // Sanity check for user_id 141 | if (!$this->created_by) 142 | { 143 | $this->created_by = 0; 144 | } 145 | 146 | if (!$this->modified) 147 | { 148 | $this->modified = $this->created; 149 | } 150 | 151 | if (empty($this->modified_by)) 152 | { 153 | $this->modified_by = $this->created_by; 154 | } 155 | 156 | return true; 157 | } 158 | 159 | /** 160 | * Generate a valid alias from title / date. 161 | * Remains public to be able to check for duplicated alias before saving 162 | * 163 | * @return string 164 | */ 165 | public function generateAlias() 166 | { 167 | if (empty($this->alias)) 168 | { 169 | $this->alias = $this->title; 170 | } 171 | 172 | $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language); 173 | 174 | if (trim(str_replace('-', '', $this->alias)) === '') 175 | { 176 | $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); 177 | } 178 | 179 | return $this->alias; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Assets/view/administrator/src/View/{{plural_capitalize}}/HtmlView.jext: -------------------------------------------------------------------------------- 1 | items = $this->get('Items'); 83 | $this->pagination = $this->get('Pagination'); 84 | $this->state = $this->get('State'); 85 | $this->filterForm = $this->get('FilterForm'); 86 | $this->activeFilters = $this->get('ActiveFilters'); 87 | 88 | $errors = $this->get('Errors'); 89 | 90 | // Check for errors. 91 | if (count($errors = $this->get('Errors'))) 92 | { 93 | throw new GenericDataException(implode("\n", $errors), 500); 94 | } 95 | 96 | $this->addToolbar(); 97 | 98 | return parent::display($tpl); 99 | } 100 | 101 | /** 102 | * Add the page title and toolbar. 103 | * 104 | * @return void 105 | * 106 | * @since {{version}} 107 | */ 108 | protected function addToolbar() 109 | { 110 | $canDo = {{component_capitalize}}Helper::getActions('{{prefix_component}}'); 111 | $user = Factory::getApplication()->getIdentity(); 112 | 113 | // Get the toolbar object instance 114 | $toolbar = Toolbar::getInstance('toolbar'); 115 | 116 | ToolbarHelper::title(Text::_('{{prefix_component_uppercase}}_{{plural_uppercase}}_TOOLBAR_LABEL'), 'book'); 117 | 118 | if ($canDo->get('core.create')) 119 | { 120 | $toolbar->addNew('{{singular}}.add'); 121 | } 122 | 123 | if ($canDo->get('core.edit.state')) 124 | { 125 | $dropdown = $toolbar->dropdownButton('status-group') 126 | ->text('JTOOLBAR_CHANGE_STATUS') 127 | ->toggleSplit(false) 128 | ->icon('icon-ellipsis-h') 129 | ->buttonClass('btn btn-action') 130 | ->listCheck(true); 131 | 132 | $childBar = $dropdown->getChildToolbar(); 133 | 134 | $childBar->publish('{{plural}}.publish')->listCheck(true); 135 | 136 | $childBar->unpublish('{{plural}}.unpublish')->listCheck(true); 137 | 138 | $childBar->archive('{{plural}}.archive')->listCheck(true); 139 | 140 | if ($user->authorise('core.admin')) 141 | { 142 | $childBar->checkin('{{plural}}.checkin')->listCheck(true); 143 | } 144 | 145 | if ((int) $this->state->get('filter.published') !== -2) 146 | { 147 | $childBar->trash('{{plural}}.trash')->listCheck(true); 148 | } 149 | } 150 | 151 | if ((int) $this->state->get('filter.published') === -2 && $canDo->get('core.delete')) 152 | { 153 | $toolbar->delete('{{plural}}.delete') 154 | ->text('JTOOLBAR_EMPTY_TRASH') 155 | ->message('JGLOBAL_CONFIRM_DELETE') 156 | ->listCheck(true); 157 | } 158 | 159 | if ($user->authorise('core.admin', '{{prefix_component}}') || $user->authorise('core.options', '{{prefix_component}}')) 160 | { 161 | $toolbar->preferences('{{prefix_component}}'); 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /src/Assets/view/administrator/src/View/{{singular_capitalize}}/HtmlView.jext: -------------------------------------------------------------------------------- 1 | form = $this->get('Form'); 63 | $this->item = $this->get('Item'); 64 | $this->state = $this->get('State'); 65 | 66 | // Check for errors. 67 | if ((count($errors = $this->get('Errors')))) 68 | { 69 | throw new GenericDataException(implode("\n", $errors), 500); 70 | } 71 | 72 | $this->addToolbar(); 73 | 74 | return parent::display($tpl); 75 | } 76 | 77 | /** 78 | * Add the page title and toolbar. 79 | * 80 | * @return void 81 | * 82 | * @since {{version}} 83 | */ 84 | protected function addToolbar() 85 | { 86 | $app = Factory::getApplication(); 87 | $app->getInput()->set('hidemainmenu', true); 88 | 89 | $user = $app->getIdentity(); 90 | $userId = $user->get('id'); 91 | $isNew = (int) $this->item->id === 0; 92 | $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId); 93 | 94 | // Since we don't track these assets at the item level, use the category id. 95 | $canDo = {{component_capitalize}}Helper::getActions('{{prefix_component}}'); 96 | 97 | ToolbarHelper::title($isNew ? Text::_('{{prefix_component_uppercase}}_{{singular_uppercase}}_NEW') : Text::_('{{prefix_component_uppercase}}_{{singular_uppercase}}_EDIT'), 'book edit-{{singular}}'); 98 | 99 | // Build the actions for new and existing records. 100 | if ($isNew) 101 | { 102 | // For new records, check the create permission. 103 | if ($isNew) 104 | { 105 | ToolbarHelper::apply('{{singular}}.apply'); 106 | 107 | ToolbarHelper::saveGroup( 108 | [ 109 | ['save', '{{singular}}.save'], 110 | ['save2new', '{{singular}}.save2new'] 111 | ], 112 | 'btn-success' 113 | ); 114 | } 115 | 116 | ToolbarHelper::cancel('{{singular}}.cancel'); 117 | } 118 | else 119 | { 120 | // Since it's an existing record, check the edit permission, or fall back to edit own if the owner. 121 | $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by === $userId); 122 | 123 | $toolbarButtons = []; 124 | 125 | // Can't save the record if it's checked out and editable 126 | if (!$checkedOut && $itemEditable) 127 | { 128 | ToolbarHelper::apply('{{singular}}.apply'); 129 | 130 | $toolbarButtons[] = ['save', '{{singular}}.save']; 131 | 132 | // We can save this record, but check the create permission to see if we can return to make a new one. 133 | if ($canDo->get('core.create')) 134 | { 135 | $toolbarButtons[] = ['save2new', '{{singular}}.save2new']; 136 | } 137 | } 138 | 139 | // If checked out, we can still save 140 | if ($canDo->get('core.create')) 141 | { 142 | $toolbarButtons[] = ['save2copy', '{{singular}}.save2copy']; 143 | } 144 | 145 | ToolbarHelper::saveGroup( 146 | $toolbarButtons, 147 | 'btn-success' 148 | ); 149 | 150 | ToolbarHelper::cancel('{{singular}}.cancel', 'JTOOLBAR_CLOSE'); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Assets/view/administrator/tmpl/{{singular}}/edit.jext: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager(); 18 | $wa->useScript('keepalive') 19 | ->useScript('form.validate'); 20 | 21 | $app = Factory::getApplication(); 22 | $input = $app->getInput(); 23 | 24 | $this->useCoreUI = true; 25 | 26 | // In case of modal 27 | $isModal = $input->get('layout') === 'modal'; 28 | $layout = $isModal ? 'modal' : 'edit'; 29 | $tmpl = $isModal || $input->get('tmpl', '', 'cmd') === 'component' ? '&tmpl=component' : ''; 30 | ?> 31 | 32 |
33 | 34 | 35 | 36 |
37 | 'details')); ?> 38 | 39 | 40 |
41 |
42 |
43 |
44 | <?php echo $this->form->renderField('fieldName'); ?> 45 |
46 |
47 |
48 |
49 |
50 |
51 | set('fields', 52 | array( 53 | 'published', 54 | 'created_by', 55 | 'created', 56 | 'access', 57 | 'language' 58 | ) 59 | ); ?> 60 | 61 | set('fields', null); ?> 62 |
63 |
64 |
65 |
66 | 67 | 68 |
69 | 70 | {{credit}} 71 | 72 | 73 | 74 | 75 |
76 | -------------------------------------------------------------------------------- /src/Assets/view/site/src/Controller/plural.jext: -------------------------------------------------------------------------------- 1 | input->getInt('id'); 48 | $this->setState('{{singular}}.id', $pk); 49 | 50 | $offset = $app->input->getUInt('limitstart'); 51 | $this->setState('list.offset', $offset); 52 | 53 | // Load the parameters. 54 | $params = $app->getParams(); 55 | $this->setState('params', $params); 56 | 57 | $this->setState('filter.language', Multilanguage::isEnabled()); 58 | } 59 | 60 | /** 61 | * Method to get {{singular}} data. 62 | * 63 | * @param integer $pk The id of the {{singular}}. 64 | * 65 | * @return object|boolean Menu item data object on success, boolean false 66 | */ 67 | public function getItem($pk = null) 68 | { 69 | $user = Factory::getApplication()->getIdentity(); 70 | 71 | $pk = (int) ($pk ?: $this->getState('{{singular}}.id')); 72 | 73 | if ($this->_item === null) 74 | { 75 | $this->_item = array(); 76 | } 77 | 78 | if (!isset($this->_item[$pk])) 79 | { 80 | try 81 | { 82 | $container = Factory::getContainer(); 83 | $app = Factory::getApplication(); 84 | $db = $container->get('DatabaseDriver'); 85 | $query = $db->getQuery(true); 86 | $user = $app->getIdentity(); 87 | 88 | $query->select( 89 | $db->quoteName( 90 | explode( 91 | ', ', 92 | $this->getState( 93 | 'list.select', 94 | 'a.id, a.title, a.alias, a.published, a.access, a.created, a.created_by, a.ordering, a.language, ' . 95 | 'a.checked_out, a.checked_out_time' 96 | ) 97 | ) 98 | ) 99 | ); 100 | 101 | $query->from($db->quoteName('#__{{component}}_{{plural}}', 'a')); 102 | 103 | $query->select($db->quoteName('l.title', 'language_title')) 104 | ->select($db->quoteName('l.image', 'language_image')) 105 | ->join( 106 | 'LEFT', 107 | $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language') 108 | ); 109 | 110 | // Join over the users for the checked out user. 111 | $query->select($db->quoteName('uc.name', 'editor')) 112 | ->join( 113 | 'LEFT', 114 | $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out') 115 | ); 116 | 117 | // Join over the asset groups. 118 | $query->select($db->quoteName('ag.title', 'access_level')) 119 | ->join( 120 | 'LEFT', 121 | $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access') 122 | ); 123 | 124 | $query->where($db->quoteName('a.id') . ' = :pk') 125 | ->bind(':pk', $pk, ParameterType::INTEGER); 126 | 127 | // Filter by access level. 128 | if ($access = $this->getState('filter.access')) 129 | { 130 | $query->where($db->quoteName('a.access') . ' = :access'); 131 | $query->bind(':access', $access, ParameterType::INTEGER); 132 | } 133 | 134 | $query->select($db->quoteName('ua.name', 'author_name')) 135 | ->join( 136 | 'LEFT', 137 | $db->quoteName('#__users', 'ua') . ' ON ' . $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by') 138 | ); 139 | 140 | // Filter by published state 141 | $published = (string) $this->getState('filter.published'); 142 | 143 | if (is_numeric($published)) 144 | { 145 | $query->where($db->quoteName('a.published') . ' = :published'); 146 | $query->bind(':published', $published, ParameterType::INTEGER); 147 | } 148 | elseif ($published === '') 149 | { 150 | $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)'); 151 | } 152 | 153 | // Filter on the language. 154 | if ($language = $this->getState('filter.language') && Multilanguage::isEnabled()) 155 | { 156 | $query->where($db->quoteName('a.language') . ' = :language'); 157 | $query->bind(':language', $language); 158 | } 159 | 160 | $db->setQuery($query); 161 | $data = $db->loadObject(); 162 | 163 | if (empty($data)) 164 | { 165 | throw new \Exception(''); 166 | } 167 | 168 | $this->_item[$pk] = $data; 169 | } 170 | catch (\Exception $e) 171 | { 172 | if ($e->getCode() == 404) 173 | { 174 | // Need to go through the error handler to allow Redirect to work. 175 | throw $e; 176 | } 177 | else 178 | { 179 | $this->setError($e); 180 | $this->_item[$pk] = false; 181 | } 182 | } 183 | } 184 | 185 | return $this->_item[$pk]; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Assets/view/site/src/View/{{plural_capitalize}}/HtmlView.jext: -------------------------------------------------------------------------------- 1 | getIdentity(); 83 | 84 | $state = $this->get('State'); 85 | $items = $this->get('Items'); 86 | $pagination = $this->get('Pagination'); 87 | 88 | // Flag indicates to not add limitstart=0 to URL 89 | $pagination->hideEmptyLimitstart = true; 90 | 91 | // Check for errors. 92 | if (count($errors = $this->get('Errors'))) 93 | { 94 | throw new GenericDataException(implode("\n", $errors), 500); 95 | } 96 | 97 | /** @var \Joomla\Registry\Registry $params */ 98 | $params = &$state->params; 99 | 100 | foreach ($items as &$item) 101 | { 102 | $item->url = RouteHelper::generateUrl('{{singular}}', $item->id, $item->alias, $item->language); 103 | } 104 | 105 | $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx')); 106 | 107 | $this->params = $params; 108 | $this->items = $items; 109 | $this->pagination = $pagination; 110 | $this->user = $user; 111 | 112 | parent::display($tpl); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Assets/view/site/src/View/{{singular_capitalize}}/HtmlView.jext: -------------------------------------------------------------------------------- 1 | getIdentity(); 82 | 83 | $state = $this->get('State'); 84 | $item = $this->get('Item'); 85 | $model = $this->getModel(); 86 | 87 | // Check for errors. 88 | if (count($errors = $this->get('Errors'))) 89 | { 90 | throw new GenericDataException(implode("\n", $errors), 500); 91 | } 92 | 93 | $params = $state->get('params'); 94 | 95 | // Escape strings for HTML output 96 | $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx')); 97 | 98 | $this->params = $params; 99 | $this->state = $state; 100 | $this->item = $item; 101 | $this->user = $user; 102 | 103 | return parent::display($tpl); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Assets/view/site/tmpl/{{plural}}/default.jext: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager(); 13 | $wa->useStyle('{{prefix_component}}.styles'); 14 | 15 | ?> 16 | 17 |
18 |
19 | items as $item): ?> 20 | 25 | 26 |
27 | pagination->getListFooter(); ?> 28 | 29 | {{credit}} 30 |
-------------------------------------------------------------------------------- /src/Assets/view/site/tmpl/{{plural}}/menu.jext: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/Assets/view/site/tmpl/{{singular}}/default.jext: -------------------------------------------------------------------------------- 1 | document->getWebAssetManager(); 13 | $wa->useStyle('{{prefix_component}}.styles'); 14 | 15 | ?> 16 | 17 |
18 |

item->title; ?>

19 | {{credit}} 20 |
-------------------------------------------------------------------------------- /src/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Controllers; 10 | 11 | use Ahamed\Jext\Utils\Printer; 12 | 13 | /** 14 | * Create the base command controller class. 15 | * 16 | * @since 1.0.0 17 | */ 18 | class BaseController 19 | { 20 | /** 21 | * Get the component metadata. 22 | * 23 | * @var array $meta The metadata associative array. 24 | * @since 1.0.0 25 | */ 26 | protected $meta = []; 27 | 28 | /** 29 | * Get the working directory. 30 | * 31 | * @var string $workingDirectory The directory. 32 | * 33 | * @since 1.0.0 34 | */ 35 | protected $workingDirectory = ''; 36 | 37 | /** 38 | * Constructor function for the command controller. 39 | * 40 | * @since 1.0.0 41 | */ 42 | public function __construct() 43 | { 44 | $this->workingDirectory = exec('pwd'); 45 | $this->meta = []; 46 | } 47 | 48 | /** 49 | * Print JEXT-CLI ASCII logo. 50 | * 51 | * @return void 52 | * 53 | * @since 1.0.0 54 | */ 55 | protected function printAsciiLogo() 56 | { 57 | $asciiArt = <<workingDirectory . '/jext.json'; 101 | 102 | if (\file_exists($metaPath)) 103 | { 104 | $this->meta = \json_decode(\file_get_contents($metaPath), true); 105 | } 106 | else 107 | { 108 | throw new \Exception('You are too early to get the meta data or the `jext.json` file has been deleted!'); 109 | } 110 | 111 | return !empty($component) ? $this->meta[$component] : $this->meta; 112 | } 113 | 114 | /** 115 | * Set meta data and write to the jext.json file. 116 | * 117 | * @param array $data The meta array. 118 | * @param string $component If data write into a specific component. 119 | * @param bool $replace If the jext data need to be completely replaced. 120 | * 121 | * @return void 122 | * 123 | * @since 1.0.0 124 | */ 125 | protected function setMeta(array $data, string $component, $replace = false) : void 126 | { 127 | try 128 | { 129 | $meta = $this->getMeta(); 130 | } 131 | catch (\Exception $e) 132 | { 133 | $meta = []; 134 | } 135 | 136 | $meta[$component] = $meta[$component] ?? []; 137 | $meta[$component] = !$replace 138 | ? array_merge($meta[$component], $data) 139 | : $data; 140 | 141 | \file_put_contents($this->workingDirectory . '/jext.json', \json_encode($meta, JSON_UNESCAPED_SLASHES)); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Controllers/HelpController.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Controllers; 10 | 11 | use Ahamed\Jext\Controllers\BaseController; 12 | use Ahamed\Jext\Interfaces\ControllerInterface; 13 | use Ahamed\Jext\Utils\Printer; 14 | 15 | /** 16 | * Create the component controller. 17 | * 18 | * @since 1.0.0 19 | */ 20 | class HelpController extends BaseController implements ControllerInterface 21 | { 22 | /** 23 | * The run function for the controller which is responsible for running a command. 24 | * 25 | * @param array $args The arguments array. 26 | * 27 | * @return void 28 | * 29 | * @since 1.0.0 30 | */ 31 | public function run(array $args = []) : void 32 | { 33 | Printer::println(Printer::getColorizeMessage("usage:", 'yellow')); 34 | Printer::println(Printer::getColorizeMessage("\tjext-cli [options] [] []", 'green')); 35 | Printer::println(); 36 | Printer::println(Printer::getColorizeMessage("Options:", 'yellow')); 37 | Printer::println(Printer::getColorizeMessage("\t-h, --help", 'green') . "\t\t\tDisplay the help message"); 38 | Printer::println(Printer::getColorizeMessage("\t-v, --version", 'green') . "\t\t\tDisplay current stable version"); 39 | Printer::println(Printer::getColorizeMessage("\t-c, --component ", 'green') . "\t\tCreate component"); 40 | Printer::println( 41 | Printer::getColorizeMessage("\t-v, --view [-f, --front]", 'green') . "\tCreate View with name and frontend view flag" 42 | ); 43 | Printer::println( 44 | Printer::getColorizeMessage("\t-v, --view [-b, --back]", 'green') . "\tCreate View with name and backend view flag" 45 | ); 46 | Printer::println( 47 | Printer::getColorizeMessage("\t-v, --view [-bt, --both]", 'green') 48 | . "\tCreate View with name and both frontend and backend view flag" 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Controllers/VersionController.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Controllers; 10 | 11 | use Ahamed\Jext\Controllers\BaseController; 12 | use Ahamed\Jext\Interfaces\ControllerInterface; 13 | use Ahamed\Jext\Utils\Printer; 14 | 15 | /** 16 | * Create the component controller. 17 | * 18 | * @since 1.0.0 19 | */ 20 | class VersionController extends BaseController implements ControllerInterface 21 | { 22 | /** 23 | * The run function for the controller which is responsible for running a command. 24 | * 25 | * @param array $args The arguments array. 26 | * 27 | * @return void 28 | * 29 | * @since 1.0.0 30 | */ 31 | public function run(array $args = []) : void 32 | { 33 | Printer::println("Jext-CLI version 1.0.0"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Interfaces/ControllerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Interfaces; 10 | 11 | /** 12 | * Controller interface. 13 | * 14 | * @since 1.0.0 15 | */ 16 | interface ControllerInterface 17 | { 18 | /** 19 | * The run function for the controller which is responsible for running a command. 20 | * 21 | * @param array $args Command arguments. 22 | * 23 | * @return void 24 | * 25 | * @since 1.0.0 26 | */ 27 | public function run(array $args = []) : void; 28 | } 29 | -------------------------------------------------------------------------------- /src/Interfaces/RegistryInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Interfaces; 10 | 11 | 12 | /** 13 | * Registry interface 14 | * 15 | * @since 1.0.0 16 | */ 17 | interface RegistryInterface 18 | { 19 | /** 20 | * Register a command. 21 | * 22 | * @param string $name The command name. 23 | * @param callable $callable The callable method which would be registered. 24 | * 25 | * @return void 26 | * 27 | * @since 1.0.0 28 | */ 29 | public function registerCommand(string $name, callable $callable) : void; 30 | 31 | /** 32 | * Register a controller. 33 | * 34 | * @param string $name The command name. 35 | * @param ControllerInterface $controller The callable method which would be registered. 36 | * 37 | * @return void 38 | * 39 | * @since 1.0.0 40 | */ 41 | public function registerController(string $name, ControllerInterface $controller) : void; 42 | 43 | /** 44 | * Get a registered command. 45 | * 46 | * @param string $name The command name. 47 | * 48 | * @return callable|ControllerInterface The registered callable or null if not found. 49 | * 50 | * @throws \InvalidArgumentException 51 | * 52 | * @since 1.0.0 53 | */ 54 | public function getRegistry(string $name); 55 | } 56 | -------------------------------------------------------------------------------- /src/Parsers/InjectionParser.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Parsers; 10 | 11 | use Ahamed\Jext\Parsers\SourceParser; 12 | use Ahamed\Jext\Utils\ComponentHelper; 13 | use Ahamed\Jext\Utils\Printer; 14 | 15 | /** 16 | * Parser class for handling the injections. 17 | * 18 | * @since 1.0.0 19 | */ 20 | class InjectionParser extends SourceParser 21 | { 22 | /** 23 | * The injection type. 24 | * 25 | * @var string $type The injection type. 26 | * 27 | * @since 1.0.0 28 | */ 29 | private $type = ''; 30 | 31 | /** 32 | * Constructor function for the InjectionParser. 33 | * 34 | * @since 1.0.0 35 | */ 36 | public function __construct() 37 | { 38 | parent::__construct(); 39 | } 40 | 41 | /** 42 | * Set the injection type. 43 | * 44 | * @param string $type The type to set. 45 | * 46 | * @return self 47 | * 48 | * @since 1.0.0 49 | */ 50 | public function setType(string $type) : InjectionParser 51 | { 52 | $this->type = $type; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Get the injection type. 59 | * 60 | * @return string The injection type. 61 | * 62 | * @since 1.0.0 63 | */ 64 | public function getType() : string 65 | { 66 | return $this->type; 67 | } 68 | 69 | /** 70 | * Parse the injected data. 71 | * @override the SourceParser parse method. 72 | * 73 | * @return void 74 | * 75 | * @since 1.0.0 76 | */ 77 | public function parse(): void 78 | { 79 | if (empty($this->srcPath) || empty($this->destPath)) 80 | { 81 | throw new \Exception(\sprintf('Please register source path and destination path before calling this "%s" method.', __METHOD__)); 82 | } 83 | 84 | $srcContent = \file_get_contents($this->srcPath); 85 | $destContent = \file_get_contents($this->destPath); 86 | 87 | if (empty($srcContent)) 88 | { 89 | throw new \Exception(\sprintf('The "%s" is invalid or the file is corrupted!', $this->srcPath)); 90 | } 91 | 92 | if (empty($destContent)) 93 | { 94 | throw new \Exception(\sprintf('The "%s" is invalid or the file is corrupted!', $this->destPath)); 95 | } 96 | 97 | $parsedContent = ComponentHelper::parseInjections( 98 | $destContent, 99 | ComponentHelper::parseContent($srcContent, $this->meta), 100 | $this->getType() 101 | ); 102 | 103 | \file_put_contents($this->destPath, $parsedContent); 104 | 105 | Printer::println(Printer::getColorizeMessage("Injected contents into " . $this->destPath . " file.", 'green')); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Parsers/SourceParser.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Parsers; 10 | 11 | use Ahamed\Jext\Utils\ComponentHelper; 12 | use Ahamed\Jext\Utils\Printer; 13 | 14 | /** 15 | * Source code parsers. 16 | * 17 | * @since 1.0.0 18 | */ 19 | class SourceParser 20 | { 21 | /** 22 | * The file source path (relative to the jext-cli root) 23 | * 24 | * @var string $srcPath The source path. 25 | * 26 | * @since 1.0.0 27 | */ 28 | protected $srcPath = ''; 29 | 30 | /** 31 | * The file destination path (relative to the project root) 32 | * 33 | * @var string $destPath The destination path. 34 | * 35 | * @since 1.0.0 36 | */ 37 | protected $destPath = ''; 38 | 39 | /** 40 | * The component meta data created before by the user. 41 | * 42 | * @var array $meta The meta information. 43 | * 44 | * @since 1.0.0 45 | */ 46 | protected $meta = []; 47 | 48 | /** 49 | * Constructor function for the source parser. 50 | * 51 | * @since 1.0.0 52 | */ 53 | public function __construct() 54 | { 55 | $workingDirectory = exec('pwd'); 56 | $metaPath = $workingDirectory . '/jext.json'; 57 | 58 | if (!\file_exists($metaPath)) 59 | { 60 | throw new \Exception(sprintf('The jext.json file is missing!')); 61 | } 62 | 63 | $this->meta = \json_decode(\file_get_contents($metaPath), true); 64 | } 65 | 66 | /** 67 | * Set meta data array. 68 | * 69 | * @param array $data The meta data. 70 | * 71 | * @return SourceParser 72 | * 73 | * @since 1.0.0 74 | */ 75 | public function setMeta(array $data) : SourceParser 76 | { 77 | $this->meta = $data; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Register the src path. 84 | * 85 | * @param string $path The source path. 86 | * 87 | * @return SourceParser for chaining. 88 | * 89 | * @since 1.0.0 90 | */ 91 | public function src(string $path) : SourceParser 92 | { 93 | $this->srcPath = $path; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Register the destination path. 100 | * 101 | * @param string $path The source path. 102 | * 103 | * @return SourceParser for chaining. 104 | * 105 | * @since 1.0.0 106 | */ 107 | public function dest(string $path) : SourceParser 108 | { 109 | $this->destPath = $path; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Run the parse operation 116 | * 117 | * 118 | * @return void 119 | * 120 | * @since 1.0.0 121 | */ 122 | public function parse() : void 123 | { 124 | if (empty($this->srcPath) || empty($this->destPath)) 125 | { 126 | throw new \Exception(\sprintf('Please register source path and destination path before calling this "%s" method.', __METHOD__)); 127 | } 128 | 129 | $srcContent = \file_get_contents($this->srcPath); 130 | 131 | if (empty($srcContent)) 132 | { 133 | throw new \Exception(\sprintf('The "%s" is invalid or the file is corrupted!', $this->srcPath)); 134 | } 135 | 136 | $parsedContent = ComponentHelper::parseContent($srcContent, $this->meta); 137 | \file_put_contents($this->destPath, $parsedContent); 138 | 139 | Printer::println(Printer::getColorizeMessage("Created " . $this->destPath . " file.", 'green')); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Registry.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext; 10 | 11 | use Ahamed\Jext\Interfaces\ControllerInterface; 12 | use Ahamed\Jext\Interfaces\RegistryInterface; 13 | 14 | /** 15 | * Registry class for registering various commands. 16 | * 17 | * @since 1.0.0 18 | */ 19 | class Registry implements RegistryInterface 20 | { 21 | /** 22 | * The registry container array. 23 | * 24 | * @var array $registry The registry array. 25 | * 26 | * @since 1.0.0 27 | */ 28 | private $registry = []; 29 | 30 | /** 31 | * Register a command. 32 | * 33 | * @param string $name The command name. 34 | * @param callable $callable The callable method which would be registered. 35 | * 36 | * @return void 37 | * 38 | * @since 1.0.0 39 | */ 40 | public function registerCommand(string $name, callable $callable) : void 41 | { 42 | $this->registry[$name] = $callable; 43 | } 44 | 45 | /** 46 | * Register a controller. 47 | * 48 | * @param string $name The command name. 49 | * @param ControllerInterface $controller The callable method which would be registered. 50 | * 51 | * @return void 52 | * 53 | * @since 1.0.0 54 | */ 55 | public function registerController(string $name, ControllerInterface $controller) : void 56 | { 57 | $this->registry[$name] = $controller; 58 | } 59 | 60 | /** 61 | * Get a registered command. 62 | * 63 | * @param string $name The command name. 64 | * 65 | * @return callable The registered callable or null if not found. 66 | * 67 | * @throws \InvalidArgumentException 68 | * 69 | * @since 1.0.0 70 | */ 71 | public function getRegistry(string $name) : callable 72 | { 73 | /** First check if there any controller registered against the name. */ 74 | $controller = isset($this->registry[$name]) ? $this->registry[$name] : null; 75 | 76 | if (!\is_null($controller)) 77 | { 78 | return [$controller, 'run']; 79 | } 80 | 81 | $command = isset($this->registry[$name]) ? $this->registry[$name] : null; 82 | 83 | if (\is_null($command)) 84 | { 85 | throw new \InvalidArgumentException(sprintf('The %s is not registered to the registry!', $name)); 86 | } 87 | 88 | return $command; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Utils/ComponentHelper.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Utils; 10 | 11 | /** 12 | * Component Helper Class 13 | * 14 | * @since 1.0.0 15 | */ 16 | class ComponentHelper 17 | { 18 | /** 19 | * Generate the component namespace 20 | * 21 | * @param string $name The component name. 22 | * @param string $client The component name. 23 | * 24 | * @return string The namespace string. 25 | * 26 | * @since 1.0.0 27 | */ 28 | public static function generateComponentNamespace(string $name, string $client = '') : string 29 | { 30 | $namespace = "Joomla\\Component\\" . ucfirst($name); 31 | $namespace = !empty($client) ? $namespace . "\\" . ucfirst($client) : $namespace; 32 | 33 | return $namespace; 34 | } 35 | 36 | /** 37 | * Modify the component name using a modifier. 38 | * The valid modifiers are [prefix, capitalize, uppercase] 39 | * 40 | * @param string $name The component name. 41 | * @param string $modifier The modifier name. 42 | * 43 | * @return string The modified name. 44 | * 45 | * @since 1.0.0 46 | */ 47 | public static function getModifiedName(string $name, string $modifier) : string 48 | { 49 | if (empty($modifier)) 50 | { 51 | return $name; 52 | } 53 | 54 | switch ($modifier) 55 | { 56 | case 'prefix': 57 | return 'com_' . $name; 58 | case 'capitalize': 59 | case 'capitalise': 60 | return ucfirst($name); 61 | case 'uppercase': 62 | return strtoupper($name); 63 | case 'prefix-upper': 64 | return strtoupper('com_' . $name); 65 | default: 66 | return $name; 67 | } 68 | 69 | return $name; 70 | } 71 | 72 | /** 73 | * Parse injections. 74 | * 75 | * @param string $content The content being parsed. 76 | * @param string $replacement The replacement content. 77 | * @param string $type The injection type. 78 | * 79 | * @return string The parsed contents. 80 | * 81 | * @since 1.0.0 82 | */ 83 | public static function parseInjections(string $content, string $replacement, string $type) : string 84 | { 85 | $regex = "@(--|;;||\*\/)?@mi"; 86 | $matches = []; 87 | $fullMatch = []; 88 | $injections = []; 89 | 90 | preg_match_all($regex, $content, $matches); 91 | 92 | /** Check if the group 2 is present in the matches. */ 93 | if (!empty($matches) && !empty($matches[2])) 94 | { 95 | $injections = $matches[2]; 96 | $fullMatch = $matches[0]; 97 | } 98 | 99 | if (!empty($injections)) 100 | { 101 | foreach ($injections as $key => $injection) 102 | { 103 | if ($type === $injection) 104 | { 105 | $content = preg_replace("@" . $fullMatch[$key] . "@i", $replacement, $content); 106 | break; 107 | } 108 | } 109 | } 110 | 111 | return $content; 112 | } 113 | 114 | /** 115 | * Parse the source content. 116 | * 117 | * @param string $content The source content. 118 | * @param array $meta The meta array. 119 | * 120 | * @return string The parsed content. 121 | * 122 | * @since 1.0.0 123 | */ 124 | public static function parseContent(string $content, array $meta) : string 125 | { 126 | if (empty($meta)) 127 | { 128 | throw new \Exception(\sprintf('You come too early for this operation!')); 129 | } 130 | 131 | extract($meta); 132 | 133 | $prefixedName = self::getModifiedName($name, 'prefix'); 134 | $capitalizeName = self::getModifiedName($name, 'capitalize'); 135 | $uppercase = self::getModifiedName($name, 'uppercase'); 136 | $prefixedUppercase = self::getModifiedName($name, 'prefix-upper'); 137 | $year = (new \DateTime)->format('Y'); 138 | $credit = '
' 142 | . '

Powered by JEXT-CLI,' 144 | . ' Developed by Sajeeb Ahamed' 145 | . '

© ' 146 | . $year . ', Sajeeb Ahamed
'; 147 | 148 | $content = preg_replace("@{{prefix_component}}@", $prefixedName, $content); 149 | $content = preg_replace("@{{prefix_component_uppercase}}@", $prefixedUppercase, $content); 150 | $content = preg_replace("@{{component_capitalize}}@", $capitalizeName, $content); 151 | $content = preg_replace("@{{component_uppercase}}@", $uppercase, $content); 152 | $content = preg_replace("@{{component}}@", $name, $content); 153 | $content = preg_replace("@{{namespace}}@", $namespace, $content); 154 | $content = preg_replace("@{{author}}@", $author, $content); 155 | $content = preg_replace("@{{creationDate}}@", $creationDate, $content); 156 | $content = preg_replace("@{{email}}@", $email, $content); 157 | $content = preg_replace("@{{url}}@", $url, $content); 158 | $content = preg_replace("@{{version}}@", $version, $content); 159 | $content = preg_replace("@{{license}}@", $license, $content); 160 | $content = preg_replace("@{{copyright}}@", $copyright, $content); 161 | $content = preg_replace("@{{description}}@", $description, $content); 162 | $content = preg_replace("@{{year}}@", $year, $content); 163 | $content = preg_replace("@{{credit}}@", $credit, $content); 164 | $content = preg_replace("@{{singular}}@", $singular, $content); 165 | $content = preg_replace("@{{plural}}@", $plural, $content); 166 | $content = preg_replace("@{{singular_capitalize}}@", \ucfirst($singular), $content); 167 | $content = preg_replace("@{{plural_capitalize}}@", \ucfirst($plural), $content); 168 | $content = preg_replace("@{{singular_uppercase}}@", \strtoupper($singular), $content); 169 | $content = preg_replace("@{{plural_uppercase}}@", \strtoupper($plural), $content); 170 | 171 | return $content; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Utils/Printer.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Utils; 10 | 11 | /** 12 | * Printer helper class 13 | * 14 | * @since 1.0.0 15 | */ 16 | final class Printer 17 | { 18 | /** 19 | * Printer color map. 20 | * 21 | * @var array $colorMap The mapped color name and value. 22 | * 23 | * @since 1.0.0 24 | */ 25 | private static $colorMap = [ 26 | 'black' => '0;30', 27 | 'blue' => '0;34', 28 | 'green' => '0;32', 29 | 'cyan' => '0;36', 30 | 'red' => '0;31', 31 | 'purple' => '0;35', 32 | 'yellow' => '1;33', 33 | 'white' => '1;37' 34 | ]; 35 | 36 | /** 37 | * The valid colors array. 38 | * 39 | * @var array $validColors The valid colors name. 40 | * 41 | * @since 1.0.0 42 | */ 43 | private static $validColors = ['black', 'blue', 'green', 'cyan', 'red', 'purple', 'yellow', 'white']; 44 | 45 | /** 46 | * Print plain message. 47 | * 48 | * @param string $message The message string to print. 49 | * 50 | * @return void 51 | * 52 | * @since 1.0.0 53 | */ 54 | public static function print(string $message) : void 55 | { 56 | echo $message; 57 | } 58 | 59 | /** 60 | * Print plain message with newline. 61 | * 62 | * @param string $message The message string to print. 63 | * 64 | * @return void 65 | * 66 | * @since 1.0.0 67 | */ 68 | public static function println(string $message = '') : void 69 | { 70 | echo ($message !== '' ? $message . "\n" : "\n"); 71 | } 72 | 73 | /** 74 | * Get colorize message. 75 | * 76 | * @param string $message The message string. 77 | * @param string $color The color name. 78 | * 79 | * @return string The colorize string. 80 | * 81 | * @since 1.0.0 82 | */ 83 | public static function getColorizeMessage(string $message, string $color) : string 84 | { 85 | if (!\in_array($color, self::$validColors)) 86 | { 87 | return $message; 88 | } 89 | 90 | return "\033[" . self::$colorMap[$color] . "m" . $message . "\033[0m"; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Utils/Utils.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace Ahamed\Jext\Utils; 10 | 11 | /** 12 | * The global utils class, 13 | * The pluralize methods functionalities get from 14 | * https://gist.github.com/tbrianjones/ba0460cc1d55f357e00b 15 | * 16 | * @since 1.0.0 17 | */ 18 | final class Utils 19 | { 20 | /** 21 | * Printer color map. 22 | * 23 | * @var array $colorMap The mapped color name and value. 24 | * 25 | * @since 1.0.0 26 | */ 27 | private static $plural = array( 28 | '/(quiz)$/i' => "$1zes", 29 | '/^(ox)$/i' => "$1en", 30 | '/([m|l])ouse$/i' => "$1ice", 31 | '/(matr|vert|ind)ix|ex$/i' => "$1ices", 32 | '/(x|ch|ss|sh)$/i' => "$1es", 33 | '/([^aeiouy]|qu)y$/i' => "$1ies", 34 | '/(hive)$/i' => "$1s", 35 | '/(?:([^f])fe|([lr])f)$/i' => "$1$2ves", 36 | '/(shea|lea|loa|thie)f$/i' => "$1ves", 37 | '/sis$/i' => "ses", 38 | '/([ti])um$/i' => "$1a", 39 | '/(tomat|potat|ech|her|vet)o$/i' => "$1oes", 40 | '/(bu)s$/i' => "$1ses", 41 | '/(alias)$/i' => "$1es", 42 | '/(octop)us$/i' => "$1i", 43 | '/(ax|test)is$/i' => "$1es", 44 | '/(us)$/i' => "$1es", 45 | '/s$/i' => "s", 46 | '/$/' => "s" 47 | ); 48 | 49 | /** 50 | * Irregular language strings. 51 | * 52 | * @var array $irregular The irregular array. 53 | * 54 | * @since 1.0.0 55 | */ 56 | private static $irregular = array( 57 | 'move' => 'moves', 58 | 'foot' => 'feet', 59 | 'goose' => 'geese', 60 | 'sex' => 'sexes', 61 | 'child' => 'children', 62 | 'man' => 'men', 63 | 'tooth' => 'teeth', 64 | 'person' => 'people', 65 | 'valve' => 'valves' 66 | ); 67 | 68 | /** 69 | * No change values. 70 | * 71 | * @var array This values are never changed. 72 | * 73 | * @since 1.0.0 74 | */ 75 | private static $noChange = array( 76 | 'sheep', 77 | 'fish', 78 | 'deer', 79 | 'series', 80 | 'species', 81 | 'money', 82 | 'rice', 83 | 'information', 84 | 'equipment' 85 | ); 86 | 87 | /** 88 | * The pluralize method. 89 | * 90 | * @param string $string The singular string. 91 | * 92 | * @return string The pluralize string. 93 | * 94 | * @since 1.0.0 95 | */ 96 | public static function pluralize(string $string) : string 97 | { 98 | $string = \strtolower($string); 99 | 100 | /** Check if the string matches with the noChanged values. */ 101 | if (\in_array($string, self::$noChange)) 102 | { 103 | return $string; 104 | } 105 | 106 | /** Check if the string matches with the irregular values. */ 107 | foreach (self::$irregular as $key => $value) 108 | { 109 | if ($key === $string) 110 | { 111 | return $value; 112 | } 113 | } 114 | 115 | /** Check on regular match cases. */ 116 | foreach (self::$plural as $regex => $replace) 117 | { 118 | if (preg_match($regex, $string)) 119 | { 120 | return preg_replace($regex, $replace, $string); 121 | } 122 | } 123 | 124 | /** If nothing matches then returns the original string. */ 125 | return $string; 126 | } 127 | } 128 | --------------------------------------------------------------------------------