├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── config └── config.php ├── phpstan.neon.dist ├── resources ├── stubs │ └── widget.stub └── views │ ├── async.blade.php │ ├── container.blade.php │ ├── loader.blade.php │ └── reloader.blade.php └── src ├── Console └── Commands │ └── WidgetMakeCommand.php ├── Exceptions └── WidgetException.php ├── Facades └── Widget.php ├── Factories └── WidgetFactory.php ├── Models ├── AbstractWidget.php └── WidgetGroup.php ├── Providers └── WidgetsServiceProvider.php └── Support └── helpers.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Rinvex Widgets Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](CONTRIBUTING.md). 6 | 7 | 8 | ## [v5.0.1] - 2020-12-25 9 | - Add support for PHP v8 10 | 11 | ## [v5.0.0] - 2020-12-22 12 | - Upgrade to Laravel v8 13 | 14 | ## [v4.1.0] - 2020-06-15 15 | - Fix async functionality issues 16 | - Fix registerCommands compatibility issue with ConsoleTools trait 17 | - Drop PHP 7.2 & 7.3 support from travis 18 | - Remove default indent size config 19 | 20 | ## [v4.0.4] - 2020-04-12 21 | - Fix ServiceProvider registerCommands method compatibility 22 | 23 | ## [v4.0.3] - 2020-04-09 24 | - Tweak artisan command registration 25 | 26 | ## [v4.0.2] - 2020-04-04 27 | - Fix namespace issue 28 | 29 | ## [v4.0.1] - 2020-04-04 30 | - Enforce consistent artisan command tag namespacing 31 | - Enforce consistent package namespace 32 | - Drop laravel/helpers usage as it's no longer used 33 | 34 | ## [v4.0.0] - 2020-03-15 35 | - Upgrade to Laravel v7.1.x & PHP v7.4.x 36 | 37 | ## [v3.0.2] - 2020-03-13 38 | - Tweak TravisCI config 39 | - Tweak service provider `publishesResources` 40 | - Update StyleCI config 41 | 42 | ## [v3.0.1] - 2019-11-07 43 | - Fix widget stub file path 44 | - Add missing composer dependency rinvex/laravel-support 45 | 46 | ## [v3.0.0] - 2019-09-23 47 | - Upgrade to Laravel v6 and update dependencies 48 | 49 | ## [v2.1.0] - 2019-06-02 50 | - Update composer deps 51 | - Drop PHP 7.1 travis test 52 | - Tweak service provider publishes functionality and console tools 53 | 54 | ## [v2.0.0] - 2019-03-03 55 | - Rename environment variable QUEUE_DRIVER to QUEUE_CONNECTION 56 | - Require PHP 7.2 & Laravel 5.8 57 | 58 | ## [v1.0.2] - 2018-12-22 59 | - Update composer dependencies 60 | - Add PHP 7.3 support to travis 61 | 62 | ## [v1.0.1] - 2018-10-05 63 | - Revert view namespace to rinvex/widgets 64 | 65 | ## [v1.0.0] - 2018-10-01 66 | - Enforce Consistency 67 | - Support Laravel 5.7+ 68 | - Rename package to rinvex/laravel-widgets 69 | 70 | ## [v0.0.3] - 2018-09-21 71 | - Update travis php versions 72 | - Drop StyleCI multi-language support (paid feature now!) 73 | - Update composer dependencies 74 | - Prepare and tweak testing configuration 75 | - Update StyleCI options 76 | - Update PHPUnit options 77 | 78 | ## [v0.0.2] - 2018-02-18 79 | - Update PHPUnit to 7.0.0 80 | - Update composer packages 81 | - Drop Laravel 5.5 support 82 | 83 | ## v0.0.1 - 2018-02-17 84 | - Tag first release 85 | 86 | [v5.0.1]: https://github.com/rinvex/laravel-widgets/compare/v5.0.0...v5.0.1 87 | [v5.0.0]: https://github.com/rinvex/laravel-widgets/compare/v4.1.0...v5.0.0 88 | [v4.1.0]: https://github.com/rinvex/laravel-widgets/compare/v4.0.4...v4.1.0 89 | [v4.0.4]: https://github.com/rinvex/laravel-widgets/compare/v4.0.3...v4.0.4 90 | [v4.0.3]: https://github.com/rinvex/laravel-widgets/compare/v4.0.2...v4.0.3 91 | [v4.0.2]: https://github.com/rinvex/laravel-widgets/compare/v4.0.1...v4.0.2 92 | [v4.0.1]: https://github.com/rinvex/laravel-widgets/compare/v4.0.0...v4.0.1 93 | [v4.0.0]: https://github.com/rinvex/laravel-widgets/compare/v3.0.2...v4.0.0 94 | [v3.0.2]: https://github.com/rinvex/laravel-widgets/compare/v3.0.1...v3.0.2 95 | [v3.0.1]: https://github.com/rinvex/laravel-widgets/compare/v3.0.0...v3.0.1 96 | [v3.0.0]: https://github.com/rinvex/laravel-widgets/compare/v2.1.0...v3.0.0 97 | [v2.1.0]: https://github.com/rinvex/laravel-widgets/compare/v2.0.0...v2.1.0 98 | [v2.0.0]: https://github.com/rinvex/laravel-widgets/compare/v1.0.1...v2.0.0 99 | [v1.0.2]: https://github.com/rinvex/laravel-widgets/compare/v1.0.0...v1.0.1 100 | [v1.0.1]: https://github.com/rinvex/laravel-widgets/compare/v1.0.0...v1.0.1 101 | [v1.0.0]: https://github.com/rinvex/laravel-widgets/compare/v0.0.3...v1.0.0 102 | [v0.0.3]: https://github.com/rinvex/laravel-widgets/compare/v0.0.2...v0.0.3 103 | [v0.0.2]: https://github.com/rinvex/laravel-widgets/compare/v0.0.1...v0.0.2 104 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [help@rinvex.com](mailto:help@rinvex.com). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | This project adheres to the following standards and practices. 4 | 5 | 6 | ## Versioning 7 | 8 | This project is versioned under the [Semantic Versioning](http://semver.org/) guidelines as much as possible. 9 | 10 | Releases will be numbered with the following format: 11 | 12 | - `..` 13 | - `..` 14 | 15 | And constructed with the following guidelines: 16 | 17 | - Breaking backward compatibility bumps the major and resets the minor and patch. 18 | - New additions without breaking backward compatibility bump the minor and reset the patch. 19 | - Bug fixes and misc changes bump the patch. 20 | 21 | 22 | ## Pull Requests 23 | 24 | The pull request process differs for new features and bugs. 25 | 26 | Pull requests for bugs may be sent without creating any proposal issue. If you believe that you know of a solution for a bug that has been filed, please leave a comment detailing your proposed fix or create a pull request with the fix mentioning that issue id. 27 | 28 | 29 | ## Coding Standards 30 | 31 | This project follows the FIG PHP Standards Recommendations compliant with the [PSR-1: Basic Coding Standard](http://www.php-fig.org/psr/psr-1/), [PSR-2: Coding Style Guide](http://www.php-fig.org/psr/psr-2/) and [PSR-4: Autoloader](http://www.php-fig.org/psr/psr-4/) to ensure a high level of interoperability between shared PHP code. If you notice any compliance oversights, please send a patch via pull request. 32 | 33 | 34 | ## Feature Requests 35 | 36 | If you have a proposal or a feature request, you may create an issue with `[Proposal]` in the title. 37 | 38 | The proposal should also describe the new feature, as well as implementation ideas. The proposal will then be reviewed and either approved or denied. Once a proposal is approved, a pull request may be created implementing the new feature. 39 | 40 | 41 | ## Git Flow 42 | 43 | This project follows [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/), and as such has `master` (latest stable releases), `develop` (latest WIP development) and X.Y support branches (when there's multiple major versions). 44 | 45 | Accordingly all pull requests MUST be sent to the `develop` branch. 46 | 47 | > **Note:** Pull requests which do not follow these guidelines will be closed without any further notice. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2021, Rinvex LLC, 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rinvex Widgets 2 | 3 | ⚠️ This package is abandoned and no longer maintained. No replacement package was suggested. ⚠️ 4 | 5 | 👉 If you are interested to step on as the main maintainer of this package, please [reach out to me](https://twitter.com/omranic)! 6 | 7 | --- 8 | 9 | **Rinvex Widgets** is a powerful and easy to use widget system, that combines both the power of code logic and the flexibility of template views. You can create asynchronous widgets, reloadable widgets, and use the console generator to auto generate your widgets, all out of the box. 10 | 11 | [![Packagist](https://img.shields.io/packagist/v/rinvex/laravel-widgets.svg?label=Packagist&style=flat-square)](https://packagist.org/packages/rinvex/laravel-widgets) 12 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/rinvex/laravel-widgets.svg?label=Scrutinizer&style=flat-square)](https://scrutinizer-ci.com/g/rinvex/laravel-widgets/) 13 | [![Travis](https://img.shields.io/travis/rinvex/laravel-widgets.svg?label=TravisCI&style=flat-square)](https://travis-ci.org/rinvex/laravel-widgets) 14 | [![StyleCI](https://styleci.io/repos/98805007/shield)](https://styleci.io/repos/98805007) 15 | [![License](https://img.shields.io/packagist/l/rinvex/laravel-widgets.svg?label=License&style=flat-square)](https://github.com/rinvex/laravel-widgets/blob/develop/LICENSE) 16 | 17 | 18 | ## Installation 19 | 20 | 1. Install the package via composer: 21 | ```shell 22 | composer require rinvex/laravel-widgets 23 | ``` 24 | 25 | 2. Done! 26 | 27 | 28 | ## Usage 29 | 30 | ### Create your widget 31 | 32 | Let's assume that we want to make a list of recent posts as a widget, and reuse it in several locations. 33 | 34 | First, we can create a Widget class using the following command: 35 | 36 | ```ssh 37 | php artisan make:widget RecentPosts 38 | ``` 39 | 40 | That command will generate a new widget class with `App\Widgets\RecentPosts` namespace, and located at `app/Widgets/RecentPosts.php` path by default. 41 | 42 | The basic content of a widget is as follows: 43 | 44 | ```php 45 | make('App\Widgets\RecentPosts'); 72 | ``` 73 | 74 | Now you can use that `$recentPosts` anywhere you want in your view, it contains the widget result. 75 | 76 | For your convenience, **Rinvex Widgets** include also a widget helper for easy usage. Example: 77 | 78 | ```blade 79 | $recentPosts = widget('App\Widgets\RecentPosts'); 80 | ``` 81 | 82 | You can also call widgets inside views using blade directive with same signature as follows: 83 | 84 | ```blade 85 | @widget('App\Widgets\RecentPosts') 86 | ``` 87 | 88 | And last but not least, there's a `Widget` facade that's automatically registered into your application, so you can use `Widget::make('App\Widgets\RecentPosts')` too. 89 | 90 | The `Widget::make()` method (and similarly all the above alternative calling methods) accepts three parameters; The first parameter is the only mandatory parameter which is the fully qualified namespace of the widget you're calling, the second parameter is for optional arguments to be passed to the widget constructor, and the third parameter is a boolean flag with true or false for asynchronous loading, will talk about that later. 91 | 92 | ### Widget parameters 93 | 94 | As mentioned before, the widget creation takes three parameters; The first parameter is the only mandatory parameter which is the fully qualified namespace of the widget you're calling, the second parameter is for optional arguments to be passed to the widget constructor, and the third parameter is a boolean flag with true or false for asynchronous loading. 95 | 96 | ```php 97 | $recentPosts = Widget::make('App\Widgets\RecentPosts', ['param1' => 'Value #1'], true); 98 | ``` 99 | 100 | As you can see, this widget is now loaded asynchronously, more about this feature later. 101 | 102 | ### Asynchronous widgets 103 | 104 | In some situations it can be very beneficial to load widget content with AJAX. 105 | 106 | Fortunately, this can be achieved very easily! All you need to do is to pass `true` as the third parameter to the Widget Facade or the blade directive. Example: `Widget::make('Widget\Class\Path', [], true)`, and simirally `@widget('Widget\Class\Path', [], true)`. 107 | 108 | > **Note:** When calling widgets asynchronously, the parameters are sent encrypted in the request, so make sure they are `json_encoded` and `json_decoded` before and afterwards. 109 | 110 | By default nothing is shown until asynchronous call is finished. This can be customized by adding a `placeholder()` method to the widget class, and return any string to be displayed. See the following example: 111 | 112 | ```php 113 | public function placeholder() 114 | { 115 | return 'Loading...'; 116 | } 117 | ``` 118 | 119 | > **Notes:** 120 | > - **Rinvex Widgets** package auto register a new route with `rinvex.widgets.async` name, which could be accessed via `http://yourproject.app/widget`. That route accepts asynchronous widget calls, with required parameters, all encrypted and process it then return the response. 121 | > - If you need to modify the default route definition or behaviour, you may need to copy `Rinvex\Widgets\Providers\WidgetsServiceProvider` to your app, modify it according to your needs and register it in your Laravel application instead of the default one. In such case you may need to disable [Laravel Auto Discovery](https://laravel.com/docs/master/packages#package-discovery) for this package **Rinvex Widgets**. 122 | 123 | ### Reloadable widgets 124 | 125 | You can go even further and automatically reload widget every N seconds. 126 | 127 | Just set the `$reloadTimeout` property of the widget class and you are done. 128 | 129 | ```php 130 | class RecentPosts extends AbstractWidget 131 | { 132 | /** 133 | * The number of seconds before each reload. 134 | * 135 | * @var float 136 | */ 137 | protected $reloadTimeout; 138 | } 139 | ``` 140 | 141 | Both synchronous and asynchronous widgets can become reloadable. You should use this feature with care, because it can easily spam your app with asynchronous calls if timeouts are too low. 142 | 143 | In case you need short response rate or realtime, you may have to consider using web sockets which is better in such case, but it's not implemented by default in this package. 144 | 145 | ### Container 146 | 147 | Asynchronous and Reloadable widgets both require some DOM interaction so they wrap all widget output in a html container. This container is defined by `AbstractWidget::container()` method and can be customized too. 148 | 149 | To make it easy, flexible, cacheable, better in performance, and even overridable, we made that container accepts a view path, so you can use whatever syntax you want in that view including of course the lovely blade tags and directives. 150 | 151 | Just set the `$container` property of the widget class and you are done. 152 | 153 | ```php 154 | class RecentPosts extends AbstractWidget 155 | { 156 | /** 157 | * The widget container template. 158 | * 159 | * @var string 160 | */ 161 | protected $container = 'rinvex/widgets::container'; 162 | } 163 | ``` 164 | 165 | > **Note:** Nesting asynchronous and reloadable widgets is currently not supported. 166 | 167 | ### Widget groups 168 | 169 | In most cases Blade is a perfect tool for setting the position and order of widgets. However, sometimes you may find it useful to approach widgets from a columns perspective by grouping them together. Check the following example: 170 | 171 | ```php 172 | // add several widgets to the 'sidebar' group 173 | Widget::group('sidebar')->addWidget(Widget::make('App\Widgets\RecentPosts'), 10); 174 | Widget::group('sidebar')->addWidget(Widget::make('App\Widgets\LatestVisitors'), 20); 175 | ``` 176 | 177 | The `Widget::addWidget()` method accepts two parameters, the first parameter is the rendered widget, which is an instance of `Illuminate\Support\HtmlString`, and the second parameter is the position where you'd like to place that widget in that group. Position?! Yes, exactly. You can order widgets inside each group, and you can imagine widget groups as a CMS columns, that way you can structure your page the way you want. 178 | 179 | To render a widget group and print the output you can do the following: 180 | 181 | ```php 182 | $sidebar = Widget::group('sidebar')->render(); 183 | ``` 184 | 185 | Or using blade syntax: 186 | 187 | ```blade 188 | @widgetGroup('sidebar') 189 | ``` 190 | 191 | And if you want to get all your widget groups collection, you can do so as follows: 192 | 193 | ```php 194 | $groups = Widget::groups(); 195 | ``` 196 | 197 | > **Notes:** 198 | > - The `Widget::group('sidebar')` returns a collection of widgets, and `Widget::groups()` returns a collection of widget groups. Both collections can be utilized exactly the same way as [Laravel Collections](https://laravel.com/docs/master/collections). 199 | 200 | You can set a separator that will be display between widgets inside the same group as follows: 201 | 202 | ```php 203 | Widget::group('sidebar')->separateWith('
'); 204 | ``` 205 | 206 | You can also wrap each widget in a group using `wrap` method like that. 207 | 208 | ```php 209 | Widget::group('sidebar')->wrapCallback(function ($key, $widget) { 210 | return "
{$widget}
"; 211 | }); 212 | ``` 213 | 214 | The `wrapCallback()` method accept a callback, that accepts two variables. The first one is the index/key of that widget in the group, the second one is the rendered content of the widget as `\Illuminate\Support\HtmlString` object. Note that you've full access to the whole widget group collection, so you can query the total number of widgets for example `$this->count()` or any other information you may need to utilize. 215 | 216 | Checking the state of a widget group: 217 | 218 | ```php 219 | // Check if widget group is empty or not 220 | Widget::group('sidebar')->isEmpty(); // bool 221 | 222 | // Get widgets count in the widget group 223 | Widget::group('sidebar')->count(); // int 224 | ``` 225 | 226 | And similarly you can check the state of all groups too: 227 | 228 | ```php 229 | // Check if there's any widget groups or not 230 | Widget::groups()->isEmpty(); // bool 231 | 232 | // Get widget groups count 233 | Widget::groups()->count(); // int 234 | ``` 235 | 236 | > **Notes:** 237 | > - The `Widget` class is a facade, that's auto registered into your Laravel application, so you may call it anywhere even inside your controllers, or views. You may call the aliased facade as `Widget` only, or call the fully qualified namespace as `Rinvex\Widgets\Facades\Widget`, no difference. 238 | > - The `Widget` facade has a fluent API, which means you can intuitively chain methods with ease. 239 | 240 | 241 | ## Changelog 242 | 243 | Refer to the [Changelog](CHANGELOG.md) for a full history of the project. 244 | 245 | 246 | ## Support 247 | 248 | The following support channels are available at your fingertips: 249 | 250 | - [Chat on Slack](https://bit.ly/rinvex-slack) 251 | - [Help on Email](mailto:help@rinvex.com) 252 | - [Follow on Twitter](https://twitter.com/rinvex) 253 | 254 | 255 | ## Contributing & Protocols 256 | 257 | Thank you for considering contributing to this project! The contribution guide can be found in [CONTRIBUTING.md](CONTRIBUTING.md). 258 | 259 | Bug reports, feature requests, and pull requests are very welcome. 260 | 261 | - [Versioning](CONTRIBUTING.md#versioning) 262 | - [Pull Requests](CONTRIBUTING.md#pull-requests) 263 | - [Coding Standards](CONTRIBUTING.md#coding-standards) 264 | - [Feature Requests](CONTRIBUTING.md#feature-requests) 265 | - [Git Flow](CONTRIBUTING.md#git-flow) 266 | 267 | 268 | ## Security Vulnerabilities 269 | 270 | If you discover a security vulnerability within this project, please send an e-mail to [help@rinvex.com](help@rinvex.com). All security vulnerabilities will be promptly contacted. 271 | 272 | 273 | ## About Rinvex 274 | 275 | Rinvex is a software solutions startup, specialized in integrated enterprise solutions for SMEs established in Alexandria, Egypt since June 2016. We believe that our drive The Value, The Reach, and The Impact is what differentiates us and unleash the endless possibilities of our philosophy through the power of software. We like to call it Innovation At The Speed Of Life. That’s how we do our share of advancing humanity. 276 | 277 | 278 | ## License 279 | 280 | This software is released under [The MIT License (MIT)](LICENSE). 281 | 282 | (c) 2016-2021 Rinvex LLC, Some rights reserved. 283 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rinvex/laravel-widgets", 3 | "description": "Rinvex Widgets is a powerful and easy to use widget system, that combines both the power of code logic and the flexibility of template views. You can create asynchronous widgets, reloadable widgets, and use the console generator to auto generate your widgets, all out of the box.", 4 | "type": "library", 5 | "keywords": [ 6 | "ajax", 7 | "async", 8 | "rinvex", 9 | "laravel", 10 | "widgets", 11 | "cache", 12 | "page", 13 | "cms" 14 | ], 15 | "license": "MIT", 16 | "homepage": "https://rinvex.com", 17 | "support": { 18 | "email": "help@rinvex.com", 19 | "issues": "https://github.com/rinvex/laravel-widgets/issues", 20 | "source": "https://github.com/rinvex/laravel-widgets", 21 | "docs": "https://github.com/rinvex/laravel-widgets/blob/master/README.md" 22 | }, 23 | "authors": [ 24 | { 25 | "name": "Rinvex LLC", 26 | "homepage": "https://rinvex.com", 27 | "email": "help@rinvex.com" 28 | }, 29 | { 30 | "name": "Abdelrahman Omran", 31 | "homepage": "https://omranic.com", 32 | "email": "me@omranic.com", 33 | "role": "Project Lead" 34 | }, 35 | { 36 | "name": "The Generous Laravel Community", 37 | "homepage": "https://github.com/rinvex/laravel-widgets/contributors" 38 | } 39 | ], 40 | "require": { 41 | "php": "^7.4.0 || ^8.0.0", 42 | "illuminate/console": "^8.0.0 || ^9.0.0", 43 | "illuminate/routing": "^8.0.0 || ^9.0.0", 44 | "illuminate/support": "^8.0.0 || ^9.0.0", 45 | "illuminate/view": "^8.0.0 || ^9.0.0", 46 | "rinvex/laravel-support": "^5.0.0" 47 | }, 48 | "require-dev": { 49 | "codedungeon/phpunit-result-printer": "^0.30.0", 50 | "illuminate/container": "^8.0.0 || ^9.0.0", 51 | "phpunit/phpunit": "^9.5.0" 52 | }, 53 | "autoload": { 54 | "files": [ 55 | "src/Support/helpers.php" 56 | ], 57 | "psr-4": { 58 | "Rinvex\\Widgets\\": "src/" 59 | } 60 | }, 61 | "autoload-dev": { 62 | "psr-4": { 63 | "Rinvex\\Widgets\\Tests\\": "tests" 64 | } 65 | }, 66 | "scripts": { 67 | "test": "vendor/bin/phpunit" 68 | }, 69 | "config": { 70 | "sort-packages": true, 71 | "preferred-install": "dist", 72 | "optimize-autoloader": true 73 | }, 74 | "extra": { 75 | "laravel": { 76 | "providers": [ 77 | "Rinvex\\Widgets\\Providers\\WidgetsServiceProvider" 78 | ], 79 | "aliases": { 80 | "Widget": "Rinvex\\Widgets\\Facades\\Widget" 81 | } 82 | } 83 | }, 84 | "minimum-stability": "dev", 85 | "prefer-stable": true 86 | } 87 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | true, 9 | 10 | ]; 11 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | parameters: 4 | level: 5 5 | paths: 6 | - src 7 | -------------------------------------------------------------------------------- /resources/stubs/widget.stub: -------------------------------------------------------------------------------- 1 | 2 | var widgetTimer{{ $params['id'] }} = setInterval(function () { 3 | if (window.$) { 4 | $('#rinvex-widgets-container-{{ $params['id'] }}').load('{{ route('rinvex.widgets.async', http_build_query($params)) }}'); 5 | clearInterval(widgetTimer{{ $params['id'] }}); 6 | } 7 | }, 100); 8 | 9 | -------------------------------------------------------------------------------- /resources/views/container.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {!! $content !!} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/loader.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/reloader.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/Console/Commands/WidgetMakeCommand.php: -------------------------------------------------------------------------------- 1 | get($name) 53 | ?? app('rinvex.widgets.group')->put($name, new WidgetGroup())->get($name); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Factories/WidgetFactory.php: -------------------------------------------------------------------------------- 1 | widget = new $widget($params); 44 | $content = $async ? $this->prepareAsyncContent() : $this->prepareContent(); 45 | 46 | return new HtmlString($this->wrapContentInContainer($content)); 47 | } 48 | 49 | /** 50 | * Wrap the given content in a container if it's not an async call. 51 | * 52 | * @param string $content 53 | * 54 | * @return string 55 | */ 56 | protected function wrapContentInContainer(string $content): string 57 | { 58 | $widget = $this->widget; 59 | 60 | return empty($widget->getParam('async')) ? view($widget->getContainer(), compact('content', 'widget'))->render() : $content; 61 | } 62 | 63 | /** 64 | * Encrypt widget params to be transported via HTTP. 65 | * 66 | * @param array $params 67 | * 68 | * @return string 69 | */ 70 | public function encryptWidgetParams(array $params = []): string 71 | { 72 | return app('encrypter')->encrypt(json_encode($params)); 73 | } 74 | 75 | /** 76 | * Decrypt widget params that were transported via HTTP. 77 | * 78 | * @param string $params 79 | * 80 | * @return array 81 | */ 82 | public function decryptWidgetParams(string $params = ''): array 83 | { 84 | return json_decode(app('encrypter')->decrypt($params), true) ?? []; 85 | } 86 | 87 | /** 88 | * Construct javascript code to load the widget. 89 | * 90 | * @param float $timeout 91 | * 92 | * @return string 93 | */ 94 | protected function getLoader(float $timeout = 0): string 95 | { 96 | $timeout = $timeout * 1000; 97 | $asyncCall = $this->constructAsyncCall(); 98 | $template = $timeout ? 'reloader' : 'loader'; 99 | 100 | return view("rinvex/widgets::{$template}", compact('timeout', 'asyncCall'))->render(); 101 | } 102 | 103 | /** 104 | * Construct async call for loaders. 105 | * 106 | * @throws \Throwable 107 | * 108 | * @return string 109 | */ 110 | protected function constructAsyncCall(): string 111 | { 112 | $params = [ 113 | 'id' => $this->widget->getId(), 114 | 'params' => $this->encryptWidgetParams($this->widget->getParams() + ['async' => true]), 115 | ]; 116 | 117 | return view('rinvex/widgets::async', compact('params'))->render(); 118 | } 119 | 120 | /** 121 | * Prepare widget content. 122 | * 123 | * @return string 124 | */ 125 | protected function prepareContent(): string 126 | { 127 | $content = app()->call([$this->widget, 'make'], $this->widget->getParams()); 128 | $content = is_object($content) ? $content->__toString() : $content; 129 | 130 | if ($timeout = $this->widget->getReloadTimeout()) { 131 | $content .= $this->getLoader($timeout); 132 | } 133 | 134 | return $content; 135 | } 136 | 137 | /** 138 | * Prepare async widget content. 139 | * 140 | * @return string 141 | */ 142 | protected function prepareAsyncContent(): string 143 | { 144 | return is_callable([$this->widget, 'placeholder']) 145 | ? call_user_func([$this->widget, 'placeholder']).$this->getLoader() : $this->getLoader(); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Models/AbstractWidget.php: -------------------------------------------------------------------------------- 1 | params = $params; 45 | $this->id = spl_object_hash($this); 46 | } 47 | 48 | /** 49 | * Get the widget reload timeout. 50 | * 51 | * @return float|null 52 | */ 53 | public function getReloadTimeout() 54 | { 55 | return $this->reloadTimeout; 56 | } 57 | 58 | /** 59 | * Set the widget reload timeout. 60 | * 61 | * @param float $reloadTimeout 62 | * 63 | * @return $this 64 | */ 65 | public function setReloadTimeout(float $reloadTimeout) 66 | { 67 | $this->reloadTimeout = $reloadTimeout; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * Get the widget id. 74 | * 75 | * @return string 76 | */ 77 | public function getId(): string 78 | { 79 | return $this->id; 80 | } 81 | 82 | /** 83 | * Set the widget id. 84 | * 85 | * @param string $id 86 | * 87 | * @return $this 88 | */ 89 | public function setId(string $id) 90 | { 91 | $this->id = $id; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Get the widget params. 98 | * 99 | * @return array 100 | */ 101 | public function getParams(): array 102 | { 103 | return $this->params; 104 | } 105 | 106 | /** 107 | * Set the widget params. 108 | * 109 | * @param array $params 110 | * 111 | * @return $this 112 | */ 113 | public function setParams(array $params) 114 | { 115 | $this->params = $params; 116 | 117 | return $this; 118 | } 119 | 120 | /** 121 | * Get a widget param. 122 | * 123 | * @return mixed 124 | */ 125 | public function getParam(string $key) 126 | { 127 | return $this->param[$key] ?? null; 128 | } 129 | 130 | /** 131 | * Set a widget param. 132 | * 133 | * @param mixed $key 134 | * @param mixed $value 135 | * 136 | * @return $this 137 | */ 138 | public function setParam($key, $value) 139 | { 140 | $this->params[$key] = $value; 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * Get the widget container. 147 | * 148 | * @return string 149 | */ 150 | public function getContainer(): string 151 | { 152 | return $this->container; 153 | } 154 | 155 | /** 156 | * Set the widget container. 157 | * 158 | * @param string $container 159 | * 160 | * @return $this 161 | */ 162 | public function setContainer(string $container) 163 | { 164 | $this->container = $container; 165 | 166 | return $this; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/Models/WidgetGroup.php: -------------------------------------------------------------------------------- 1 | sortBy('position')->transform(function ($widget, $key) { 35 | $content = $this->performWrap($key, $widget); 36 | 37 | return $this->keys()->last() !== $key ? $this->separator.$content : $content; 38 | })->reduce(function ($carry, $widget) { 39 | return $carry.$widget; 40 | }); 41 | 42 | return new HtmlString($output); 43 | } 44 | 45 | /** 46 | * Add a widget to the group. 47 | * 48 | * @param \Rinvex\Widgets\Models\AbstractWidget $widget 49 | * @param int $position 50 | * 51 | * @return $this 52 | */ 53 | public function addWidget(AbstractWidget $widget, int $position = 100) 54 | { 55 | $this->offsetSet($position, $widget); 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Set a separator to display between widgets in the group. 62 | * 63 | * @param string $separator 64 | * 65 | * @return $this 66 | */ 67 | public function separateWith(string $separator) 68 | { 69 | $this->separator = $separator; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Set the callback that defines extra markup 76 | * that wraps every widget in the group. 77 | * 78 | * @param callable $callable 79 | * 80 | * @return $this 81 | */ 82 | public function wrapCallback(callable $callable) 83 | { 84 | $this->wrapCallback = $callable; 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Execute the callback that defines extra markup that 91 | * wraps every widget in the group with a special markup. 92 | * 93 | * @param int $key 94 | * @param \Illuminate\Support\HtmlString $widget 95 | * 96 | * @return string 97 | */ 98 | protected function performWrap(int $key, HtmlString $widget): string 99 | { 100 | return is_callable($callback = $this->wrapCallback) ? $callback($key, $widget) : $widget; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Providers/WidgetsServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'command.rinvex.widgets.make', 25 | ]; 26 | 27 | /** 28 | * Register the service provider. 29 | * 30 | * @return void 31 | */ 32 | public function register(): void 33 | { 34 | // Merge config 35 | $this->mergeConfigFrom(realpath(__DIR__.'/../../config/config.php'), 'rinvex.widgets'); 36 | 37 | $this->registerWidgetFactory(); 38 | $this->registerWidgetCollection(); 39 | 40 | // Register console commands 41 | $this->registerCommands($this->commands); 42 | } 43 | 44 | /** 45 | * Register the widget collection. 46 | * 47 | * @return void 48 | */ 49 | public function registerWidgetCollection(): void 50 | { 51 | // Register widget collection 52 | $this->app->singleton('rinvex.widgets.list', function ($app) { 53 | return collect(); 54 | }); 55 | } 56 | 57 | /** 58 | * Register the widget factory. 59 | * 60 | * @return void 61 | */ 62 | public function registerWidgetFactory(): void 63 | { 64 | $this->app->singleton('rinvex.widgets', WidgetFactory::class); 65 | 66 | $this->app->singleton('rinvex.widgets.group', function () { 67 | return collect(); 68 | }); 69 | } 70 | 71 | /** 72 | * Bootstrap the application events. 73 | * 74 | * @return void 75 | */ 76 | public function boot(Router $router): void 77 | { 78 | // Load resources 79 | $this->loadRoutes($router); 80 | $this->loadViewsFrom(__DIR__.'/../../resources/views', 'rinvex/widgets'); 81 | 82 | // Publish Resources 83 | $this->publishesViews('rinvex/laravel-widgets'); 84 | $this->publishesConfig('rinvex/laravel-widgets'); 85 | 86 | // Register blade extensions 87 | $this->registerBladeExtensions(); 88 | } 89 | 90 | /** 91 | * Load the routes. 92 | * 93 | * @param \Illuminate\Routing\Router $router 94 | * 95 | * @return void 96 | */ 97 | protected function loadRoutes(Router $router): void 98 | { 99 | // Load routes 100 | if (! $this->app->routesAreCached() && config('rinvex.widgets.register_routes')) { 101 | $router->get('widget', function () { 102 | $factory = app('rinvex.widgets'); 103 | $widgetName = urldecode(request()->input('name')); 104 | $widgetParams = $factory->decryptWidgetParams(request()->input('params', '')); 105 | 106 | return call_user_func_array([$factory, $widgetName], $widgetParams); 107 | })->name('rinvex.widgets.async')->middleware('web'); 108 | 109 | $this->app->booted(function () use ($router) { 110 | $router->getRoutes()->refreshNameLookups(); 111 | $router->getRoutes()->refreshActionLookups(); 112 | }); 113 | } 114 | } 115 | 116 | /** 117 | * Register console commands. 118 | * 119 | * @param array $commands 120 | * 121 | * @return void 122 | */ 123 | protected function registerCommands(array $commands): void 124 | { 125 | // Register artisan commands 126 | $this->app->singleton('command.rinvex.widgets.make', function ($app) { 127 | return new WidgetMakeCommand($app['files']); 128 | }); 129 | 130 | $this->commands(array_values($commands)); 131 | } 132 | 133 | /** 134 | * Register the blade extensions. 135 | * 136 | * @return void 137 | */ 138 | protected function registerBladeExtensions(): void 139 | { 140 | $this->app->afterResolving('blade.compiler', function (BladeCompiler $bladeCompiler) { 141 | // @widget('App\Widgets\ExampleWidget', $paramArray, $asyncFlag) 142 | $bladeCompiler->directive('widget', function ($expression) { 143 | return "make({$expression}); ?>"; 144 | }); 145 | 146 | // @widgetGroup('widgetGroupName') 147 | $bladeCompiler->directive('widgetGroup', function ($expression) { 148 | return "group({$expression})->render(); ?>"; 149 | }); 150 | }); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Support/helpers.php: -------------------------------------------------------------------------------- 1 | make($widget, $params, $async); 26 | } 27 | } 28 | --------------------------------------------------------------------------------