├── CHANGELOG.md
├── CODE-OF-CONDUCT.md
├── DOCUMENTATION.md
├── LICENSE
├── Plugin.php
├── README.md
├── ServiceProvider.php
├── assets
├── css
│ ├── redirect.css
│ ├── statistics.css
│ └── test-lab.css
├── images
│ ├── extensions
│ │ └── Vdlp.RedirectConditions.svg
│ └── icon.svg
└── javascript
│ └── test-lab.js
├── classes
├── BrandHelper.php
├── CacheManager.php
├── OptionHelper.php
├── PublishManager.php
├── RedirectConditionManager.php
├── RedirectManager.php
├── RedirectManagerSettings.php
├── RedirectMiddleware.php
├── RedirectRule.php
├── Sparkline.php
├── StatisticsHelper.php
├── TesterBase.php
├── TesterResult.php
├── contracts
│ ├── CacheManagerInterface.php
│ ├── PublishManagerInterface.php
│ ├── RedirectConditionInterface.php
│ ├── RedirectManagerInterface.php
│ └── TesterInterface.php
├── exceptions
│ ├── InvalidScheme.php
│ ├── NoMatchForRequest.php
│ ├── NoMatchForRule.php
│ ├── RulesPathNotReadable.php
│ ├── RulesPathNotWritable.php
│ └── UnableToLoadRules.php
├── observers
│ ├── RedirectObserver.php
│ ├── SettingsObserver.php
│ └── traits
│ │ └── CanBeDisabled.php
├── testers
│ ├── RedirectCount.php
│ ├── RedirectFinalDestination.php
│ ├── RedirectLoop.php
│ ├── RedirectMatch.php
│ └── ResponseCode.php
└── util
│ └── Str.php
├── composer.json
├── config
└── config.php
├── console
└── PublishRedirectsCommand.php
├── controllers
├── Categories.php
├── Extensions.php
├── Logs.php
├── Redirects.php
├── Statistics.php
├── TestLab.php
├── categories
│ ├── _list_toolbar.htm
│ ├── config_form.yaml
│ ├── config_list.yaml
│ ├── create.htm
│ ├── index.htm
│ └── update.htm
├── extensions
│ ├── _installed.htm
│ └── index.htm
├── logs
│ ├── _list_toolbar.htm
│ ├── config_filter.yaml
│ ├── config_list.yaml
│ └── index.htm
├── redirects
│ ├── _field_statistics.htm
│ ├── _list_toolbar.htm
│ ├── _popup_actions.htm
│ ├── _redirect_test.htm
│ ├── _redirect_test_result.htm
│ ├── _reorder_toolbar.htm
│ ├── _sparkline.htm
│ ├── _status_code_info.htm
│ ├── _tab_logs.htm
│ ├── _warning.htm
│ ├── config_filter.yaml
│ ├── config_form.yaml
│ ├── config_import_export.yaml
│ ├── config_list.yaml
│ ├── config_relation.yaml
│ ├── config_reorder.yaml
│ ├── create.htm
│ ├── export.htm
│ ├── import.htm
│ ├── index.htm
│ ├── reorder.htm
│ ├── request-log
│ │ ├── _list_toolbar.htm
│ │ ├── _modal.htm
│ │ ├── columns.yaml
│ │ └── config_list.yaml
│ └── update.htm
├── statistics
│ ├── _hits-per-day.htm
│ ├── _loading-indicator.htm
│ ├── _redirect-hits-per-month.htm
│ ├── _score-board.htm
│ ├── _top-crawlers-this-month.htm
│ ├── _top-redirects-this-month.htm
│ └── index.htm
└── testlab
│ ├── _test_button.htm
│ ├── _tester_failed.htm
│ ├── _tester_result.htm
│ ├── _tester_result_items.htm
│ └── index.htm
├── lang
├── de
│ └── lang.php
├── en
│ └── lang.php
├── es
│ └── lang.php
├── fr
│ └── lang.php
├── nl
│ └── lang.php
├── ru
│ └── lang.php
└── sv
│ └── lang.php
├── models
├── Category.php
├── Client.php
├── Redirect.php
├── RedirectExport.php
├── RedirectImport.php
├── RedirectLog.php
├── Settings.php
├── category
│ ├── columns.yaml
│ └── fields.yaml
├── client
│ ├── columns.yaml
│ └── fields.yaml
├── redirect
│ ├── columns.yaml
│ └── fields.yaml
├── redirectexport
│ └── columns.yaml
├── redirectimport
│ └── columns.yaml
├── redirectlog
│ └── columns.yaml
└── settings
│ └── fields.yaml
├── reportwidgets
├── CreateRedirect.php
├── TopTenRedirects.php
├── createredirect
│ ├── fields.yaml
│ └── partials
│ │ └── _widget.htm
└── toptenredirects
│ └── partials
│ └── _widget.htm
├── routes.php
└── updates
├── 20180718_0001_create_tables.php
├── 20180831_0002_upgrade_from_adrenth_redirect.php
├── 20181019_0003_add_ignore_query_parameters_to_redirects_table.php
├── 20181117_0004_add_redirect_timestamp_crawler_index_on_clients_table.php
├── 20181117_0005_add_month_year_crawler_index_on_clients_table.php
├── 20190404_0006_add_description_to_redirects_table.php
├── 20190704_0007_add_timestamp_crawler_index_on_clients_table.php
├── 20200408_0008_change_column_types_from_char_to_varchar.php
├── 20200414_0009_add_ignore_case_to_redirects_table.php
├── 20200414_0010_add_ignore_trailing_slash_to_redirects_table.php
├── 20200918_0011_refactor_redirects_logs_table.php
├── 20200918_0012_add_redirect_id_to_system_request_logs_table.php
├── 20231108_0013_add_keep_querystring_to_redirects_table.php
└── version.yaml
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [3.1.11] - 2023-11-30
8 |
9 | * Add option to keep query string when redirecting.
10 |
11 | ## [3.1.10] - 2023-09-28
12 |
13 | * Fixed backend filtering for MySQL >= 8.0.3.
14 |
15 | ## [3.1.9] - 2023-06-12
16 |
17 | * Added environment variables for manipulating the navigation:
18 | * `VDLP_REDIRECT_SHOW_IMPORT`
19 | * `VDLP_REDIRECT_SHOW_EXPORT`
20 | * `VDLP_REDIRECT_SHOW_SETTINGS`
21 | * `VDLP_REDIRECT_SHOW_EXTENSION`
22 | * UI optimizations
23 |
24 | ## [3.1.8] - 2023-05-25
25 |
26 | * Add German translation (#104).
27 |
28 | ## [3.1.7] - 2023-04-19
29 |
30 | * Remove use of old BrandSetting constants (#103).
31 |
32 | ## [3.1.6] - 2023-03-28
33 |
34 | * Add support for October CMS 3.3
35 | * UI optimizations
36 |
37 | ## [3.1.5] - 2023-03-20
38 |
39 | * Prevent 'Uninitialized string offset 0' error.
40 | * Updated Chart.js library to v4.2.1
41 |
42 | ## [3.1.4] - 2023-02-14
43 |
44 | * Fix target field not loading properly.
45 |
46 | ## [3.1.3] - 2023-01-27
47 |
48 | * Minor code improvements.
49 |
50 | ## [3.1.2] - 2022-12-09
51 |
52 | * Fix daily stats labels when selecting month/year.
53 |
54 | ## [3.1.1] - 2022-06-24
55 |
56 | * Add description column to Redirects overview.
57 |
58 | ## [3.1.0] - 2022-06-11
59 |
60 | * Add support for October CMS 3.x.
61 | * Drop support for October CMS 2.x.
62 | * Minimum required PHP version is now 8.0.2
63 |
64 | ## [3.0.5] - 2022-06-11
65 |
66 | * Lock to October CMS version 2.x. Support for October CMS 3 will be added in v3.1.0.
67 |
68 | ## [3.0.4] - 2022-04-19
69 |
70 | * Improved compatibility/extensibility with other Plugins (Solves issue #90).
71 |
72 | ## [3.0.3] - 2022-03-15
73 |
74 | * Update plugin dependencies.
75 |
76 | ## [3.0.2] - 2022-02-20
77 |
78 | * Change version constraint for composer/installers.
79 |
80 | ## [3.0.1] - 2022-02-20
81 |
82 | * Add support for regular expression matches in target path.
83 |
84 | ## [3.0.0] - 2022-02-19
85 |
86 | * Drop support for October CMS 1.1 and lower.
87 | * Minimum required PHP version is now 7.4
88 | * Redirect to relative paths is now enabled by default.
89 | * Add database relation to system request logs.
90 | * When updating to `3.0.0` the table `vdlp_redirect_redirect_logs` will be removed due to schema changes.
91 | * Improvements to redirect request logs.
92 | * Improvements to redirect conditions logic.
93 | * Improvements to the statistics page.
94 | * Redirect Settings:
95 | * Relative paths setting is now **enabled** by default.
96 | * Testlab (Beta) is now **disabled** by default.
97 | * Redirect logging is now **disabled** by default.
98 | * Redirect statistics is now **disabled** by default.
99 |
100 | ## [2.6.0]
101 |
102 | * Update plugin dependencies.
103 |
104 | ## [2.5.13]
105 |
106 | * Fix database error when cache is being cleared before installation of plugin.
107 |
108 | ## [2.5.12]
109 |
110 | * Fix strpos() type error.
111 |
112 | ## [2.5.11]
113 |
114 | * Remove CMS support check.
115 | * Fix bad use of import.
116 |
117 | ## [2.5.10]
118 |
119 | * Add PHP 8.0 version constraint.
120 | * Add composer/installers package.
121 |
122 | ## [2.5.9]
123 |
124 | * Fix import in Plugin file.
125 |
126 | ## [2.5.8]
127 |
128 | * Improve redirect caching management (revised).
129 |
130 | ## [2.5.7]
131 |
132 | * Improve redirect caching management.
133 |
134 | ## [2.5.6]
135 |
136 | * Prevent connection exception when accessing settings in CLI mode.
137 |
138 | ## [2.5.5]
139 |
140 | * Suppress logging when redirect rules file is empty.
141 |
142 | ## [2.5.4]
143 |
144 | * Add support for symfony/stopwatch:^5.0 (version 4.0 is still supported).
145 | * Update Spanish language (thanks to Juan David M).
146 | * Hide button "From Request log" when request logging is disabled.
147 |
148 | ## [2.5.3]
149 |
150 | * Improve / fixes redirect rule caching (thanks to Eric Pfeiffer).
151 | * Update Spanish language (thanks to Juan David M).
152 | * Update Language files (help wanted!).
153 |
154 | ## [2.5.2]
155 |
156 | * Fix bug that causes re-writing the redirect rules file when hits are updated.
157 |
158 | ## [2.5.1]
159 |
160 | * Fixes issues with redirect rules file not being present.
161 |
162 | ## [2.5.0]
163 |
164 | * Add support for using relative paths.
165 |
166 | ## [2.4.1]
167 |
168 | * Add Redirect Extensions promo page.
169 |
170 | ## [2.4.0]
171 |
172 | * Skip requests with header "X-Requested-With: XMLHttpRequest".
173 |
174 | ## [2.3.2]
175 |
176 | * Improve error handling in plugin migration process.
177 |
178 | ## [2.3.1]
179 |
180 | * Fix SQLSTATE[42S22] error when installing plugin.
181 |
182 | ## [2.3.0]
183 |
184 | * Add new Redirect options:
185 | * Ignore Case
186 | * Ignore Trailing Slashes
187 | * Fix date field timezone issue (scheduled tab).
188 | * Fix warning dialog position (when scheduled redirect is not active).
189 | * Minor translation improvements (en, nl).
190 |
191 | ## [2.2.0]
192 |
193 | * Add "Cache-Control: no-store" header. This will prevent (modern) web browsers to cache the redirects. Very convenient when testing your redirects.
194 | * Add extra tab "Event logs" to Redirect update page. This tab shows a list with the related event logs of the redirect.
195 | * UI improvements.
196 |
197 | ## [2.1.1]
198 |
199 | * Update CHANGELOG.
200 |
201 | ## [2.1.0]
202 |
203 | * Improve exception handling #52.
204 | * Add support for league/csv:9.0+.
205 | * Improve caching mechanism #54.
206 | * Suppress cache flush log message.
207 | * Skip sparkline routes from being processed.
208 |
209 | ## [2.0.2]
210 |
211 | * Force type of vdlp.redirect::log_redirect_changes #53.
212 | * Apply config check to prevent log redirect changes #53.
213 | * Convert database column types (char to varchar) #51.
214 |
215 | ## [2.0.1]
216 |
217 | * Fix Middleware not being invoked in newer PHP versions.
218 |
219 | ## [2.0.0]
220 |
221 | * Drop support for PHP 7.0, only supports PHP 7.1.3+.
222 | * Most of the classes are made final. For extending use October CMS proposed solutions.
223 | * Auto-redirect creation for CMS/Static pages has been removed from this plugin.
224 | * The following events have been removed:
225 | * `vdlp.redirects.changed`
226 | * `vdlp.redirect.beforeRedirectSave`
227 | * `vdlp.redirect.beforeRedirectUpdate`
228 | * `vdlp.redirect.afterRedirectUpdate`
229 | * New events:
230 | * `vdlp.redirect.changed`
231 | * `vdlp.redirect.changed`
232 |
--------------------------------------------------------------------------------
/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, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and 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 within all project spaces, and it also applies when
49 | an individual is representing the project or its community in public spaces.
50 | Examples of representing a project or community include using an official
51 | project e-mail address, posting via an official social media account, or acting
52 | as an appointed representative at an online or offline event. Representation of
53 | a project may be 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 octobercms@vdlp.nl. 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 https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/DOCUMENTATION.md:
--------------------------------------------------------------------------------
1 | # Vdlp.Redirect documentation
2 |
3 | This plugin should be easy to understand if you are familiar with the basics of the web. If you have issues setting up some redirects, please do not hesitate to contact me.
4 |
5 | ## Redirect types
6 |
7 | This plugins ships with two types of redirects:
8 |
9 | * **Exact**; performs an exact match on the Source path.
10 | * **Placeholders**; matches placeholders like {id} or {category} (like the defined routes in Symfony or Laravel framework).
11 | * **Regular expression**; Use regular expressions to match multiple patterns at once (advanced).
12 |
13 | ## Redirect target types
14 |
15 | This plugin allows you to redirect to the following types:
16 |
17 | * An internal path
18 | * An internal CMS Page
19 | * An internal Static Page (`RainLab.Pages` plugin)
20 | * An external URL
21 |
22 | ## Relative vs. Absolute URLs
23 |
24 | Both types are supported by the Redirect plugin. Absolute URLs are generated by default.
25 | This setting can be changed at the Redirect Settings page.
26 |
27 | In some cases it is necessary to use Relative URLs only. When using a reverse proxy for example. Or when you are
28 | hosting multiple domains on one single codebase.
29 |
30 | Example when Absolute URLs are enabled (default):
31 |
32 | ```
33 | Source path: /path/from
34 | Target path: /path/to
35 | Result path when matched: https://example.com/path/to
36 | ```
37 |
38 | Example when Relative URLs are enabled:
39 |
40 | ```
41 | Source path: /path/from
42 | Target path: /path/to
43 | Result path when matched: /path/to
44 | ```
45 |
46 | ## Scheme matching
47 |
48 | This plugin allows you to match requests from a `http://` scheme to a `https://` scheme and vice versa.
49 |
50 | ## Placeholders
51 |
52 | Every placeholder can be attached to a requirement. A requirement consists of a `placeholder`, `requirement` and an optional `replacement` value.
53 |
54 | Example:
55 |
56 | ```
57 | Input path:
58 | /blog.php?category=cat&id=145
59 |
60 | Source path:
61 | /blog.php?category={category}&id={id}
62 |
63 | Target path:
64 | /blog/{category}/{id}
65 |
66 | Result path:
67 | /blog/cat/145
68 | ```
69 |
70 | * The requirement for `{category}` would be: `[a-zA-Z]` or could be more specific like `(dog|cat|mouse)`.
71 | * The requirement for `{id}` would be: `[0-9]+`.
72 |
73 | **Replacement value**
74 |
75 | A requirement can also contain a replacement value. Provide this replacement value if you need to rewrite a certain placeholder to a static value.
76 |
77 | Example:
78 |
79 | The requirement for `{category}` is `(dog|cat|mouse)`, with replacement value `animals`.
80 |
81 | ```
82 | Input path:
83 | /blog.php?category=mouse&id=1337
84 |
85 | Source path:
86 | /blog.php?category={category}&id={id}
87 |
88 | Target path:
89 | /blog/{category}/{id}
90 |
91 | Result:
92 | /blog/animals/1337
93 | ```
94 |
95 | 
96 |
97 | Result in TestLab:
98 |
99 | 
100 |
101 | ## Regular expression (advanced)
102 |
103 | For advanced users there's the Regular Expression matching logic. Please refer to the PHP.net `preg_match` manual.
104 |
105 | The actual `$matches` result from the `preg_match($sourcePath, $url, $matches)` function can be used in the target path and will be replaced with the matched value.
106 |
107 | Example (with matches replacement):
108 |
109 | ```
110 | Input path: /foo/my-match
111 | Source Path: @/foo/(.*)?@
112 | Target Path: /bar/{1}
113 | Result: /bar/my-match
114 | ```
115 |
116 | ## Redirect Target
117 |
118 | You can select a CMS Page as a Redirect target. Placeholders are supported. Let's assume there is a page 'Blog' with the following URL: `/blog/:category/:subcategory`.
119 |
120 | It is possible to create a Redirect with placeholders that has this CMS Page as a target:
121 |
122 | ````
123 | Redirect with:
124 | Source: `/blog.php?cat={category}&subcat={subcategory}`
125 | Placeholders: {category}, {subcategory}
126 | Target: CMS Page `Blog`
127 |
128 | Request path: /blog.php?cat=news&subcat=general
129 | Result: /blog/news/general
130 | ````
131 |
132 | ## Events
133 |
134 | ### Fires events
135 |
136 | | Event | Payload | Description |
137 | | --- | --- | --- |
138 | | `vdlp.redirect.match` | none | When a request matched, right before the redirect response.
139 | | `vdlp.redirect.changed` | int[] $redirectId | When one or more redirects are changed.
140 |
141 | ### Listens to events
142 |
143 | | Event | Payload | Description |
144 | | --- | --- | --- |
145 | | `vdlp.redirect.toUrlChanged` | `string $oldUrl, string $newUrl` | Can be fired from a third-party plugin.
146 |
147 | ## Commands
148 |
149 | | Command | Description |
150 | | --- | --- |
151 | | `vdlp:redirect:publish-redirects` | Publish all redirects. |
152 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Vdlp.Redirect
4 |
5 |
6 |
7 | Manage all your HTTP redirects with an easy to use GUI. This is an essential SEO plugin.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## The #1 Redirect plugin for October CMS
20 |
21 | This is the best Redirect-plugin for October CMS. With this plugin installed you can manage redirects directly from October CMS' beautiful interface. Many webmasters and SEO specialists use redirects to optimise their website for search engines. This plugin allows you to manage such redirects with a nice and user-friendly interface.
22 |
23 | ## History
24 |
25 | This plugin was originally build in 2016 by Alwin Drenth a Software Engineer at Van der Let & Partners.
26 | As of 2018 this plugin is re-distributed to the October CMS Marketplace with vendor name Vdlp.Redirect (formerly known as Adrenth.Redirect).
27 |
28 | The Redirect plugin will now be maintained by Van der Let & Partners and You (the open source community).
29 |
30 | ## What does this plugin offer?
31 |
32 | This plugin adds a 'Redirects' section to the main menu of October CMS. This plugin has a unique and fast matching algorithm to match your redirects before your website is being rendered.
33 |
34 | ## Features
35 |
36 | * **Quick** matching algorithm
37 | * A **test** utility for redirects
38 | * Matching using **placeholders** (dynamic paths)
39 | * Matching using **regular expressions**
40 | * **Exact** path matching
41 | * **Importing** and **exporting** redirect rules
42 | * **Schedule** redirects (e.g. active for 2 months)
43 | * Redirect to **external** URLs
44 | * Redirect to **internal** CMS pages
45 | * Redirect to relative or absolute URLs
46 | * Redirect **log**
47 | * **Categorize** redirects
48 | * **Statistics**
49 | * Hits per redirect
50 | * Popular redirects per month (top 10)
51 | * Popular crawlers per month (top 10)
52 | * Number of redirects per month
53 | * And more...
54 | * Multilingual ***(Need help translating! Contact us at octobercms@vdlp.nl)***
55 | * Supports MySQL, SQLite and Postgres
56 | * HTTP status codes 301, 302, 303, 404, 410
57 | * Caching
58 |
59 | ## Supported database platforms
60 |
61 | * MySQL
62 | * Postgres
63 | * SQLite
64 |
65 | ## Requirements
66 |
67 | * October CMS 3
68 | * PHP version 8.0.2 or higher.
69 | * PHP extensions: `ext-curl` and `ext-json`.
70 |
71 | ## Supported HTTP status codes
72 |
73 | * `HTTP/1.1 301 Moved Permanently`
74 | * `HTTP/1.1 302 Found`
75 | * `HTTP/1.1 303 See Other`
76 | * `HTTP/1.1 404 Not Found`
77 | * `HTTP/1.1 410 Gone`
78 |
79 | ## Supported HTTP request methods
80 |
81 | * `GET`
82 | * `POST`
83 | * `HEAD`
84 |
85 | ## Performance
86 |
87 | All redirects are stored in the database and will be automatically "published" to a file which the internal redirect mechanism uses to determine if a certain request needs to be redirected. This is way faster than querying a database.
88 |
89 | This plugin is designed to be fast and should have no negative effect on the performance of your website.
90 |
91 | To gain maximum performance with this plugin:
92 |
93 | * Enable redirect caching using a "in-memory" caching method (see Caching).
94 | * Maintain your redirects frequently to keep the number of redirects as low as possible.
95 | * Try to use placeholders to keep your number of redirect low (less redirects is better performance).
96 |
97 | ## Caching
98 |
99 | If your website has a lot of redirects it is recommended to enable redirect caching. You can enable redirect caching in the settings panel of this plugin.
100 |
101 | Only cache drivers which support tagged cache are supported. So driver `file` and `database` are not supported. For this plugin database and file caching do not increase performance, but can actually have a negative influence on performance. So it is recommended to use an in-memory caching solution like `memcached` or `redis`.
102 |
103 | ### How caching works
104 |
105 | If caching is enabled (and supported) every request which is handled by this plugin will be cached. It will be stored with tag `Vdlp.Redirect`.
106 |
107 | When you modify a redirect all redirect cache will be invalidated automatically. It is also possible to manually clear the cache using the 'Clear cache' button in the Backend.
108 |
109 | ## Placeholders
110 |
111 | This plugin makes advantage of the `symfony/routing` package. So if you need more info on how to make placeholder requirements for your redirection URLs, please go to: https://symfony.com/doc/current/components/routing/introduction.html#usage
112 |
113 | ## Contribution
114 |
115 | Please feel free to [contribute](https://github.com/vdlp/oc-redirect-plugin) to this awesome plugin.
116 |
117 | ## Questions? Need help?
118 |
119 | If you have any question about how to use this plugin, please don't hesitate to contact us at octobercms@vdlp.nl. We're happy to help you. You can also visit the support forum and drop your questions/issues there.
120 |
121 | ---
122 |
123 | > If you love this quality plugin as much as we do, please [**rate our plugin**](http://octobercms.com/plugin/vdlp-redirect).
124 |
125 | ---
126 |
--------------------------------------------------------------------------------
/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->bind(Contracts\RedirectManagerInterface::class, RedirectManager::class);
22 | $this->app->bind(Contracts\PublishManagerInterface::class, PublishManager::class);
23 | $this->app->bind(Contracts\CacheManagerInterface::class, CacheManager::class);
24 |
25 | $this->app->singleton(RedirectManager::class);
26 | $this->app->singleton(PublishManager::class);
27 | $this->app->singleton(CacheManager::class);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/assets/css/redirect.css:
--------------------------------------------------------------------------------
1 | .modal-content h3 {
2 | color: #da5700;
3 | margin-bottom: 8px;
4 | }
5 |
6 | #Form-field-Redirect-status_code-group .help-block.before-field {
7 | margin-bottom: 0;
8 | color: #2a3e51;
9 | }
10 |
11 | .status-code-info {
12 | cursor: pointer;
13 | margin-bottom: 10px;
14 | color: #da5700;
15 | }
16 |
17 | tr.special td {
18 | font-style: italic;
19 | }
20 |
21 | div.actions .btn {
22 | text-align: center;
23 | }
24 |
25 | .sparkline {
26 | width: 100px;
27 | height: 30px;
28 | background-size: 100px 30px;
29 | background-repeat: no-repeat;
30 | }
31 |
32 | .sparkline img {
33 | width: 100px;
34 | height: 30px;
35 | opacity: .8;
36 | }
37 |
38 | .form-sidebar .sparkline {
39 | width: 100%;
40 | height: 120px;
41 | background-size: 260px 120px;
42 | background-repeat: no-repeat;
43 | margin-top: 4px;
44 | }
45 |
46 | .form-sidebar .sparkline img {
47 | width: 100%;
48 | height: 120px;
49 | opacity: .8;
50 | }
51 |
52 | .scoreboard-item.title-value p {
53 | font-size: 20px;
54 | }
55 |
56 | ul.nav li a:focus, table.data tr:focus {
57 | outline:0 !important;
58 | box-shadow: none !important;
59 | }
60 |
--------------------------------------------------------------------------------
/assets/css/statistics.css:
--------------------------------------------------------------------------------
1 | .row {
2 | margin-bottom: 30px;
3 | }
4 |
5 | .row .report-widget {
6 | position: relative;
7 | min-height: 155px;
8 | }
9 |
10 | .row .report-widget h3 {
11 | padding-bottom: 5px;
12 | border-bottom: 1px solid #cae1e2;
13 | margin-bottom: 15px;
14 | }
15 |
16 | .report-widget.redirect-hits-per-day {
17 | min-height: 375px;
18 | }
19 |
20 | .report-widget.current-active-redirects {
21 | min-height: 375px;
22 | }
23 |
24 | .report-widget.general {
25 | min-height: 400px;
26 | }
27 |
28 | .report-widget div[data-control=toolbar] {
29 | height: 125px;
30 | }
31 |
32 | .report-widget .loading-indicator-container {
33 | position: absolute;
34 | left: 0;
35 | top: 0;
36 | width: 100%;
37 | height: 100%;
38 | }
39 |
40 | .report-widget .loading-indicator {
41 | background: transparent;
42 | }
43 |
--------------------------------------------------------------------------------
/assets/css/test-lab.css:
--------------------------------------------------------------------------------
1 | .test-lab {
2 | height: 100%;
3 | width: 100%;
4 | position: absolute;
5 | top: 0;
6 | }
7 |
8 | #testerResults {
9 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
10 | font-size: 12px;
11 | position: relative;
12 | top: 0;
13 | overflow: scroll;
14 | height: 85%;
15 | outline: none;
16 | }
17 |
18 | #testButton {
19 | text-align: center;
20 | }
21 |
22 | /*
23 | * Progress Bar
24 | **********************************************************************************************************************/
25 |
26 | .progress {
27 | height: 35.7891px;
28 | margin-bottom: 4px;
29 | }
30 |
31 | .progress-bar {
32 |
33 | }
34 |
35 | /*
36 | * Tester Results
37 | **********************************************************************************************************************/
38 |
39 | #testerResults a {
40 | outline: none;
41 | }
42 |
43 | #testerResults .failed i,
44 | #testerResults .failed .test {
45 | color: #ff0000;
46 | }
47 |
48 | #testerResults .passed i,
49 | #testerResults .passed .test {
50 | color: #009900;
51 | }
52 |
53 | #testerResults .row.heading .toolbar {
54 | font-family: sans-serif;
55 | }
56 |
57 | #testerResults .row.heading .toolbar .toolbar-item {
58 | margin-left: 4px;
59 | font-size: 12px;
60 | }
61 |
62 | #testerResults .row.heading {
63 | margin-bottom: 4px;
64 | }
65 |
66 | #testerResults .tester-result {
67 | background-color: rgb(233, 237, 243);
68 | border: 1px solid #fafbfd;
69 | border-radius: 4px;
70 | padding: 8px;
71 | margin-bottom: 20px;
72 | }
73 |
74 | /*
75 | * Loading Indicator
76 | **********************************************************************************************************************/
77 |
78 | #testerResults .loading-indicator {
79 | font-size: 12px;
80 | }
81 |
82 | #testerResults .loading-indicator-container {
83 | min-height: 34px;
84 | }
85 |
86 | #testerResults .loading-indicator > span {
87 | background-size: 20px 20px;
88 | right: 0;
89 | left: auto;
90 | }
91 |
92 | #testerResults .loading-indicator-container .loading-indicator > div {
93 | right: 0;
94 | margin-right: 40px;
95 | }
96 |
97 | /*
98 | * Popup Loading Indicator
99 | **********************************************************************************************************************/
100 |
101 | .popup-backdrop .popup-loading-indicator {
102 | width: 200px;
103 | height: 200px;
104 | margin-left: -100px;
105 | }
106 |
107 | .popup-backdrop .popup-loading-indicator:after {
108 | margin-left: 75px;
109 | }
110 |
111 | .popup-backdrop .popup-loading-indicator .btn {
112 | position: absolute;
113 | width: 100px;
114 | left: 50%;
115 | margin-left: -50px;
116 | text-align: center;
117 | top: 150px;
118 | }
119 |
120 | .popup-backdrop .popup-loading-indicator div {
121 | position: absolute;
122 | width: 200px;
123 | left: 50%;
124 | margin-left: -100px;
125 | text-align: center;
126 | top: 110px;
127 | }
128 |
--------------------------------------------------------------------------------
/assets/images/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tile
8 |
9 |
10 | A
11 | B
12 |
13 |
--------------------------------------------------------------------------------
/assets/javascript/test-lab.js:
--------------------------------------------------------------------------------
1 | var testerShouldStop = false;
2 |
3 | function testerExecute(offset, total, button) {
4 | if (testerShouldStop) {
5 | testerDone();
6 | testerShouldStop = false;
7 | return;
8 | }
9 |
10 | $.request('onTest', {
11 | data: {
12 | offset: offset
13 | },
14 | success: function (data) {
15 | if (data.result === '' || typeof data.result === 'undefined') {
16 | testerDone();
17 | updateStatusBar(total, total);
18 | return;
19 | }
20 |
21 | $('#testerResults').prepend(data.result);
22 |
23 | updateStatusBar(total, offset);
24 |
25 | if (offset + 1 !== total) {
26 | testerExecute(offset + 1, total, button);
27 | }
28 | },
29 | error: function() {
30 | if (offset + 1 !== total) {
31 | testerExecute(offset + 1, total, button);
32 | }
33 | }
34 | });
35 | }
36 |
37 | function testerDone() {
38 | $('#testButton').prop('disabled', false);
39 |
40 | var loader = $('#loader');
41 | loader.removeClass('loading');
42 |
43 | setTimeout(function () {
44 | loader.addClass('hidden');
45 | }, 500);
46 | }
47 |
48 | function testerStart(button) {
49 | updateStatusBar(0);
50 |
51 | $('#testerResults').html('');
52 |
53 | button.prop('disabled', true);
54 |
55 | var loader = $('#loader');
56 | loader.removeClass('hidden');
57 | loader.addClass('loading');
58 |
59 | testerExecute(0, $('#redirectCount').val(), button);
60 | }
61 |
62 | function testerStop() {
63 | testerShouldStop = true;
64 | }
65 |
66 | function updateStatusBar(total, offset) {
67 | var width = 0;
68 |
69 | if (total > 0) {
70 | width = Math.ceil(100 / total * offset);
71 | }
72 |
73 | var progress = $('#progress');
74 | progress.html(width + '% complete (' + offset + ' of ' + total + ')');
75 |
76 | var progressBar = $('#progressBar');
77 | progressBar.attr('aria-valuenow', width);
78 | progressBar.css('width', width + '%');
79 |
80 | if (width === 0) {
81 | progress.html(progress.data('initial'));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/classes/BrandHelper.php:
--------------------------------------------------------------------------------
1 | 3.3
22 | if (method_exists($brandSettings, 'getPaletteColors')) {
23 | $colorPalette = BrandSetting::get('color_palette');
24 | $colorMode = BrandSetting::getColorMode();
25 |
26 | $this->primaryColor = $colorPalette[$colorMode]['primary'] ?? '#6a6cf7';
27 | $this->secondaryColor = $colorPalette[$colorMode]['secondary'] ?? '#72809d';
28 | // October CMS <3.2
29 | } else {
30 | $this->primaryColor = $brandSettings->get('primary_color');
31 | $this->secondaryColor = $brandSettings->get('secondary_color');
32 | }
33 | }
34 |
35 | public function getPrimaryColor(): string
36 | {
37 | return $this->primaryColor;
38 | }
39 |
40 | public function getSecondaryColor(): string
41 | {
42 | return $this->secondaryColor;
43 | }
44 |
45 | public function getPrimaryOrSecondaryColor(bool $flag): string
46 | {
47 | return $flag ? $this->getPrimaryColor() : $this->getSecondaryColor();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/classes/CacheManager.php:
--------------------------------------------------------------------------------
1 | cache->tags(self::CACHE_TAG_MATCHES)
30 | ->get($key);
31 | }
32 |
33 | public function forget(string $key): bool
34 | {
35 | return $this->cache->tags(self::CACHE_TAG_MATCHES)
36 | ->forget($key);
37 | }
38 |
39 | public function has(string $key): bool
40 | {
41 | return $this->cache->tags(self::CACHE_TAG_MATCHES)
42 | ->has($key);
43 | }
44 |
45 | public function cacheKey(string $requestPath, string $scheme): string
46 | {
47 | // Most caching backend have no limits on key lengths.
48 | // But to be sure I chose to MD5 hash the cache key.
49 | return md5($requestPath . $scheme);
50 | }
51 |
52 | public function flush(): void
53 | {
54 | $this->cache->tags([self::CACHE_TAG, self::CACHE_TAG_RULES, self::CACHE_TAG_MATCHES])
55 | ->flush();
56 |
57 | if ((bool) config('vdlp.redirect::log_redirect_changes', false) === true) {
58 | $this->log->info('Vdlp.Redirect: Redirect cache has been flushed.');
59 | }
60 | }
61 |
62 | public function putRedirectRules(array $redirectRules): void
63 | {
64 | $this->cache->tags(self::CACHE_TAG_RULES)
65 | ->forever('rules', $redirectRules);
66 | }
67 |
68 | public function getRedirectRules(): array
69 | {
70 | if (!$this->cache->tags(self::CACHE_TAG_RULES)->has('rules')) {
71 | $publishManager = resolve(PublishManagerInterface::class);
72 | $publishManager->publish();
73 | }
74 |
75 | $data = $this->cache->tags(self::CACHE_TAG_RULES)
76 | ->get('rules', []);
77 |
78 | if (is_array($data)) {
79 | return $data;
80 | }
81 |
82 | return [];
83 | }
84 |
85 | public function putMatch(string $cacheKey, ?RedirectRule $matchedRule = null): ?RedirectRule
86 | {
87 | if ($matchedRule === null) {
88 | $this->cache->tags(self::CACHE_TAG_MATCHES)
89 | ->forever($cacheKey, false);
90 |
91 | return null;
92 | }
93 |
94 | $matchedRuleToDate = $matchedRule->getToDate();
95 |
96 | if ($matchedRuleToDate instanceof Carbon) {
97 | $minutes = $matchedRuleToDate->diffInMinutes(Carbon::now());
98 |
99 | $this->cache->tags(self::CACHE_TAG_MATCHES)
100 | ->put($cacheKey, $matchedRule, $minutes);
101 | } else {
102 | $this->cache->tags(self::CACHE_TAG_MATCHES)
103 | ->forever($cacheKey, $matchedRule);
104 | }
105 |
106 | return $matchedRule;
107 | }
108 |
109 | public function cachingEnabledAndSupported(): bool
110 | {
111 | if (!Settings::isCachingEnabled()) {
112 | return false;
113 | }
114 |
115 | try {
116 | $this->cache->tags(self::CACHE_TAG);
117 | } catch (Throwable) {
118 | return false;
119 | }
120 |
121 | return true;
122 | }
123 |
124 | public function cachingEnabledButNotSupported(): bool
125 | {
126 | if (!Settings::isCachingEnabled()) {
127 | return false;
128 | }
129 |
130 | try {
131 | $this->cache->tags(self::CACHE_TAG);
132 | } catch (Throwable) {
133 | return true;
134 | }
135 |
136 | return false;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/classes/OptionHelper.php:
--------------------------------------------------------------------------------
1 | 'vdlp.redirect::lang.redirect.target_type_none',
20 | ];
21 | }
22 |
23 | return [
24 | Redirect::TARGET_TYPE_PATH_URL => 'vdlp.redirect::lang.redirect.target_type_path_or_url',
25 | Redirect::TARGET_TYPE_CMS_PAGE => 'vdlp.redirect::lang.redirect.target_type_cms_page',
26 | Redirect::TARGET_TYPE_STATIC_PAGE => 'vdlp.redirect::lang.redirect.target_type_static_page',
27 | ];
28 | }
29 |
30 | public static function getCmsPageOptions(): array
31 | {
32 | return ['' => '-- ' . e(trans('vdlp.redirect::lang.redirect.none')) . ' --' ] + Page::getNameList();
33 | }
34 |
35 | public static function getStaticPageOptions(): array
36 | {
37 | $options = ['' => '-- ' . e(trans('vdlp.redirect::lang.redirect.none')) . ' --' ];
38 |
39 | $hasPagesPlugin = PluginManager::instance()->hasPlugin('RainLab.Pages');
40 |
41 | if (!$hasPagesPlugin) {
42 | return $options;
43 | }
44 |
45 | $pages = \RainLab\Pages\Classes\Page::listInTheme(Theme::getActiveTheme());
46 |
47 | /** @var \RainLab\Pages\Classes\Page $page */
48 | foreach ($pages as $page) {
49 | if (array_key_exists('title', $page->viewBag)) {
50 | $options[$page->getBaseFileName()] = $page->viewBag['title'];
51 | }
52 | }
53 |
54 | return $options;
55 | }
56 |
57 | public static function getCategoryOptions(): array
58 | {
59 | return Category::query()
60 | ->get(['id', 'name'])
61 | ->pluck('name', 'key')
62 | ->toArray();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/classes/PublishManager.php:
--------------------------------------------------------------------------------
1 | where('is_enabled', 1)
49 | ->orderBy('sort_order')
50 | ->get($columns);
51 |
52 | if ($this->cacheManager->cachingEnabledAndSupported()) {
53 | $this->publishToCache($redirects->toArray());
54 | } else {
55 | $this->publishToFilesystem($columns, $redirects->toArray());
56 | }
57 |
58 | $count = $redirects->count();
59 |
60 | if ((bool) config('vdlp.redirect::log_redirect_changes', false) === true) {
61 | $this->log->info(sprintf(
62 | 'Vdlp.Redirect: Redirect engine has been updated with %s redirects.',
63 | $count
64 | ));
65 | }
66 |
67 | return $count;
68 | }
69 |
70 | private function publishToFilesystem(array $columns, array $redirects): void
71 | {
72 | $redirectsFile = config('vdlp.redirect::rules_path');
73 |
74 | if (file_exists($redirectsFile)) {
75 | unlink($redirectsFile);
76 | }
77 |
78 | try {
79 | $writer = Writer::createFromPath($redirectsFile, 'w+');
80 | $writer->insertOne($columns);
81 |
82 | foreach ($redirects as $row) {
83 | if (isset($row['requirements'])) {
84 | $row['requirements'] = json_encode($row['requirements'], JSON_THROW_ON_ERROR);
85 | }
86 |
87 | $writer->insertOne($row);
88 | }
89 | } catch (Throwable $throwable) {
90 | touch($redirectsFile);
91 |
92 | $this->log->error($throwable);
93 | }
94 | }
95 |
96 | private function publishToCache(array $redirects): void
97 | {
98 | foreach ($redirects as &$redirect) {
99 | if (!isset($redirect['requirements'])) {
100 | continue;
101 | }
102 |
103 | try {
104 | $redirect['requirements'] = json_encode($redirect['requirements'], JSON_THROW_ON_ERROR);
105 | } catch (JsonException) {
106 | // @ignoreException
107 | }
108 | }
109 |
110 | unset($redirect);
111 |
112 | $this->cacheManager->flush();
113 | $this->cacheManager->putRedirectRules($redirects);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/classes/RedirectConditionManager.php:
--------------------------------------------------------------------------------
1 | redirectManager->getConditions();
26 |
27 | if (count($conditions) === 0) {
28 | return $enabledConditions;
29 | }
30 |
31 | $conditionCodes = \Vdlp\RedirectConditions\Models\ConditionParameter::query()
32 | ->where('redirect_id', '=', $rule->getId())
33 | ->whereNotNull('is_enabled')
34 | ->get(['condition_code'])
35 | ->pluck('condition_code')
36 | ->toArray();
37 |
38 | if (count($conditionCodes) === 0) {
39 | return $enabledConditions;
40 | }
41 |
42 | foreach ($conditions as $condition) {
43 | /** @var RedirectConditionInterface $condition */
44 | $condition = resolve($condition);
45 |
46 | if (!in_array($condition->getCode(), $conditionCodes, true)) {
47 | continue;
48 | }
49 |
50 | $enabledConditions[] = $condition;
51 | }
52 |
53 | return $enabledConditions;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/classes/RedirectManagerSettings.php:
--------------------------------------------------------------------------------
1 | loggingEnabled;
30 | }
31 |
32 | public function isStatisticsEnabled(): bool
33 | {
34 | return $this->statisticsEnabled;
35 | }
36 |
37 | public function isRelativePathsEnabled(): bool
38 | {
39 | return $this->relativePathsEnabled;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/classes/RedirectMiddleware.php:
--------------------------------------------------------------------------------
1 | isXmlHttpRequest()
38 | || !in_array($request->method(), self::$supportedMethods, true)
39 | || Str::startsWith($request->getRequestUri(), '/vdlp/redirect/sparkline/')
40 | ) {
41 | return $next($request);
42 | }
43 |
44 | if ($request->header('X-Vdlp-Redirect') === 'Tester') {
45 | $this->redirectManager->setSettings(new RedirectManagerSettings(
46 | false,
47 | false,
48 | Settings::isRelativePathsEnabled()
49 | ));
50 | }
51 |
52 | $rule = false;
53 |
54 | $requestUri = str_replace($request->getBasePath(), '', $request->getRequestUri());
55 |
56 | try {
57 | if (
58 | $this->cacheManager->cachingEnabledAndSupported()
59 | && method_exists($this->redirectManager, 'matchCached')
60 | ) {
61 | $rule = $this->redirectManager->matchCached($requestUri, $request->getScheme());
62 | } else {
63 | $rule = $this->redirectManager->match($requestUri, $request->getScheme());
64 | }
65 | } catch (NoMatchForRequest | UnableToLoadRules | InvalidScheme) {
66 | // @ignoreException
67 | $rule = false;
68 | } catch (Throwable $throwable) {
69 | $this->log->error(sprintf(
70 | 'Vdlp.Redirect: Could not perform redirect for %s (scheme: %s): %s',
71 | $requestUri,
72 | $request->getScheme(),
73 | $throwable->getMessage()
74 | ));
75 | }
76 |
77 | if ($rule === false || $rule === null) {
78 | return $next($request);
79 | }
80 |
81 | /*
82 | * Extensibility:
83 | *
84 | * At this point a positive match was made based on the request URI.
85 | */
86 | $this->dispatcher->fire('vdlp.redirect.match', [$rule, $requestUri]);
87 |
88 | /*
89 | * Extensibility:
90 | *
91 | * Developers can add their own conditions. If a condition does not pass the redirect will be ignored.
92 | */
93 | foreach ($this->redirectConditionManager->getEnabledConditions($rule) as $condition) {
94 | if (!$condition->passes($rule, $requestUri)) {
95 | return $next($request);
96 | }
97 | }
98 |
99 | $this->redirectManager->redirectWithRule($rule, $requestUri);
100 |
101 | return $next($request);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/classes/Sparkline.php:
--------------------------------------------------------------------------------
1 | colorHexToRGB($color);
18 |
19 | $baseRed = $baseGreen = $baseBlue = 255;
20 |
21 | $red = (int) floor(($baseRed + $red) / 2);
22 | $green = (int) floor(($baseGreen + $green) / 2);
23 | $blue = (int) floor(($baseBlue + $blue) / 2);
24 |
25 | $this->setFillColorRGB($red, $green, $blue);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/classes/TesterBase.php:
--------------------------------------------------------------------------------
1 | testUrl = url($testPath, [], $secure);
32 | }
33 |
34 | final public function execute(): TesterResult
35 | {
36 | $stopwatch = new Stopwatch();
37 |
38 | $stopwatch->start(__FUNCTION__);
39 |
40 | $result = $this->test();
41 |
42 | $event = $stopwatch->stop(__FUNCTION__);
43 |
44 | $result->setDuration((int) $event->getDuration());
45 |
46 | return $result;
47 | }
48 |
49 | public function getTestPath(): string
50 | {
51 | return $this->testPath;
52 | }
53 |
54 | public function getTestUrl(): string
55 | {
56 | return $this->testUrl;
57 | }
58 |
59 | abstract protected function test(): TesterResult;
60 |
61 | protected function setDefaultCurlOptions(CurlHandle $curlHandle): void
62 | {
63 | curl_setopt($curlHandle, CURLOPT_MAXREDIRS, self::MAX_REDIRECTS);
64 | curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, self::CONNECTION_TIMEOUT);
65 | curl_setopt($curlHandle, CURLOPT_AUTOREFERER, true);
66 |
67 | // This constant is not available when open_basedir or safe_mode are enabled.
68 | curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true);
69 |
70 | /** @noinspection CurlSslServerSpoofingInspection */
71 | curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, false);
72 | /** @noinspection CurlSslServerSpoofingInspection */
73 | curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, false);
74 |
75 | if (defined('CURLOPT_SSL_VERIFYSTATUS')) {
76 | curl_setopt($curlHandle, CURLOPT_SSL_VERIFYSTATUS, false);
77 | }
78 |
79 | curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
80 | curl_setopt($curlHandle, CURLOPT_VERBOSE, false);
81 | curl_setopt($curlHandle, CURLOPT_HTTPHEADER, [
82 | 'X-Vdlp-Redirect: Tester',
83 | ]);
84 | }
85 |
86 | protected function getRedirectManager(): RedirectManagerInterface
87 | {
88 | /** @var RedirectManagerInterface $manager */
89 | $manager = resolve(RedirectManagerInterface::class);
90 |
91 | return $manager->setSettings(new RedirectManagerSettings(
92 | false,
93 | false,
94 | Settings::isRelativePathsEnabled()
95 | ));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/classes/TesterResult.php:
--------------------------------------------------------------------------------
1 | duration = 0;
16 | }
17 |
18 | public function isPassed(): bool
19 | {
20 | return $this->passed;
21 | }
22 |
23 | public function getMessage(): string
24 | {
25 | return $this->message;
26 | }
27 |
28 | public function setDuration(int $duration): TesterResult
29 | {
30 | $this->duration = $duration;
31 |
32 | return $this;
33 | }
34 |
35 | public function getDuration(): int
36 | {
37 | return $this->duration;
38 | }
39 |
40 | public function getStatusCssClass(): string
41 | {
42 | return $this->passed ? 'passed' : 'failed';
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/classes/contracts/CacheManagerInterface.php:
--------------------------------------------------------------------------------
1 | getId(),
20 | $requestPath,
21 | $scheme ?: '(no scheme)'
22 | ));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/classes/exceptions/RulesPathNotReadable.php:
--------------------------------------------------------------------------------
1 | logChange($model, 'created');
34 |
35 | $this->dispatcher->dispatch('vdlp.redirect.changed', [
36 | 'redirectIds' => Arr::wrap($model->getKey())
37 | ]);
38 | }
39 |
40 | /**
41 | * @param Models\Redirect $model
42 | * @return void
43 | */
44 | public function updated(Models\Redirect $model): void
45 | {
46 | if (!self::canHandleChanges()) {
47 | return;
48 | }
49 |
50 | $this->logChange($model, 'updated');
51 |
52 | $this->dispatcher->dispatch('vdlp.redirect.changed', [
53 | 'redirectIds' => Arr::wrap($model->getKey())
54 | ]);
55 | }
56 |
57 | /**
58 | * @param Models\Redirect $model
59 | * @return void
60 | */
61 | public function deleted(Models\Redirect $model): void
62 | {
63 | if (!self::canHandleChanges()) {
64 | return;
65 | }
66 |
67 | $this->logChange($model, 'deleted');
68 |
69 | $this->dispatcher->dispatch('vdlp.redirect.changed', [
70 | 'redirectIds' => Arr::wrap($model->getKey())
71 | ]);
72 | }
73 |
74 | private function logChange(Models\Redirect $model, string $typeOfChange): void
75 | {
76 | if ((bool) config('vdlp.redirect::log_redirect_changes', false) === false) {
77 | return;
78 | }
79 |
80 | $this->log->info(sprintf(
81 | 'Vdlp.Redirect: Redirect %d has been %s.',
82 | $model->getKey(),
83 | $typeOfChange
84 | ), $model->getDirty());
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/classes/observers/SettingsObserver.php:
--------------------------------------------------------------------------------
1 | publishManager->publish();
21 | } catch (Throwable) {
22 | // @ignoreException
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/classes/observers/traits/CanBeDisabled.php:
--------------------------------------------------------------------------------
1 | testUrl);
15 |
16 | if ($curlHandle === false) {
17 | return new TesterResult(false, e(trans('vdlp.redirect::lang.test_lab.test_error')));
18 | }
19 |
20 | $this->setDefaultCurlOptions($curlHandle);
21 |
22 | $error = null;
23 |
24 | if (curl_exec($curlHandle) === false) {
25 | $error = curl_error($curlHandle);
26 | }
27 |
28 | if ($error !== null) {
29 | return new TesterResult(false, e(trans('vdlp.redirect::lang.test_lab.result_request_failed')));
30 | }
31 |
32 | $statusCode = (int) curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
33 | $redirectCount = (int) curl_getinfo($curlHandle, CURLINFO_REDIRECT_COUNT);
34 |
35 | curl_close($curlHandle);
36 |
37 | return new TesterResult(
38 | $redirectCount === 1 || ($redirectCount === 0 && $statusCode > 400),
39 | e(trans('vdlp.redirect::lang.test_lab.redirects_followed', ['count' => $redirectCount, 'limit' => 10]))
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/classes/testers/RedirectFinalDestination.php:
--------------------------------------------------------------------------------
1 | testUrl);
15 |
16 | if ($curlHandle === false) {
17 | return new TesterResult(false, e(trans('vdlp.redirect::lang.test_lab.test_error')));
18 | }
19 |
20 | $this->setDefaultCurlOptions($curlHandle);
21 |
22 | curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false);
23 |
24 | $error = null;
25 |
26 | if (curl_exec($curlHandle) === false) {
27 | $error = e(trans('vdlp.redirect::lang.test_lab.not_determinate_destination_url'));
28 | }
29 |
30 | $finalDestination = curl_getinfo($curlHandle, CURLINFO_REDIRECT_URL);
31 | $statusCode = (int) curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
32 |
33 | curl_close($curlHandle);
34 |
35 | if (empty($finalDestination) && $statusCode > 400) {
36 | $message = $error ?? e(trans('vdlp.redirect::lang.test_lab.no_destination_url'));
37 | } else {
38 | $finalDestination = sprintf(
39 | '%s ',
40 | e($finalDestination),
41 | e($finalDestination)
42 | );
43 |
44 | $message = $error
45 | ?? trans('vdlp.redirect::lang.test_lab.final_destination_is', ['destination' => $finalDestination]);
46 | }
47 |
48 | return new TesterResult($error === null, $message);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/classes/testers/RedirectLoop.php:
--------------------------------------------------------------------------------
1 | testUrl);
15 |
16 | if ($curlHandle === false) {
17 | return new TesterResult(false, e(trans('vdlp.redirect::lang.test_lab.test_error')));
18 | }
19 |
20 | $this->setDefaultCurlOptions($curlHandle);
21 |
22 | curl_setopt($curlHandle, CURLOPT_MAXREDIRS, 20);
23 |
24 | $error = null;
25 |
26 | if (
27 | curl_exec($curlHandle) === false
28 | && curl_errno($curlHandle) === CURLE_TOO_MANY_REDIRECTS
29 | ) {
30 | $error = e(trans('vdlp.redirect::lang.test_lab.possible_loop'));
31 | }
32 |
33 | curl_close($curlHandle);
34 |
35 | $message = $error ?? e(trans('vdlp.redirect::lang.test_lab.no_loop'));
36 |
37 | return new TesterResult($error === null, $message);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/classes/testers/RedirectMatch.php:
--------------------------------------------------------------------------------
1 | getRedirectManager();
20 |
21 | try {
22 | $match = $manager->match(
23 | $this->testPath,
24 | $this->secure ? Redirect::SCHEME_HTTPS : Redirect::SCHEME_HTTP
25 | );
26 | } catch (NoMatchForRequest | InvalidScheme | UnableToLoadRules) {
27 | $match = false;
28 | }
29 |
30 | if ($match === false) {
31 | return new TesterResult(false, e(trans('vdlp.redirect::lang.test_lab.not_match_redirect')));
32 | }
33 |
34 | $message = sprintf(
35 | '%s %s .',
36 | e(trans('vdlp.redirect::lang.test_lab.matched')),
37 | Backend::url('vdlp/redirect/redirects/update/' . $match->getId()),
38 | e(trans('vdlp.redirect::lang.test_lab.redirect'))
39 | );
40 |
41 | return new TesterResult(true, $message);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/classes/testers/ResponseCode.php:
--------------------------------------------------------------------------------
1 | testUrl);
27 |
28 | if ($curlHandle === false) {
29 | return new TesterResult(false, e(trans('vdlp.redirect::lang.test_lab.test_error')));
30 | }
31 |
32 | $this->setDefaultCurlOptions($curlHandle);
33 |
34 | curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false);
35 |
36 | $error = null;
37 |
38 | if (curl_exec($curlHandle) === false) {
39 | $error = curl_error($curlHandle);
40 | }
41 |
42 | if ($error !== null) {
43 | return new TesterResult(false, e(trans('vdlp.redirect::lang.test_lab.result_request_failed')));
44 | }
45 |
46 | $statusCode = (int) curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
47 |
48 | curl_close($curlHandle);
49 |
50 | $manager = $this->getRedirectManager();
51 |
52 | try {
53 | $match = $manager->match(
54 | $this->testPath,
55 | $this->secure ? Redirect::SCHEME_HTTPS : Redirect::SCHEME_HTTP
56 | );
57 | } catch (NoMatchForRequest | InvalidScheme | UnableToLoadRules) {
58 | $match = false;
59 | }
60 |
61 | if ($match !== false && $match->getStatusCode() !== $statusCode) {
62 | $message = e(trans('vdlp.redirect::lang.test_lab.matched_not_http_code', [
63 | 'expected' => $match->getStatusCode(),
64 | 'received' => $statusCode,
65 | ]));
66 |
67 | return new TesterResult(false, $message);
68 | }
69 |
70 | if ($match !== false && $match->getStatusCode() === $statusCode) {
71 | $message = e(trans('vdlp.redirect::lang.test_lab.matched_http_code', [
72 | 'code' => $statusCode,
73 | ]));
74 |
75 | return new TesterResult(true, $message);
76 | }
77 |
78 | // Should be a 301, 302, 303, 404, 410, ...
79 | if (!array_key_exists($statusCode, Redirect::$statusCodes)) {
80 | return new TesterResult(
81 | false,
82 | e(trans('vdlp.redirect::lang.test_lab.response_http_code_should_be'))
83 | . ' '
84 | . implode(', ', array_keys(Redirect::$statusCodes))
85 | );
86 | }
87 |
88 | return new TesterResult(
89 | true,
90 | e(trans('vdlp.redirect::lang.test_lab.response_http_code')) . ': ' . $statusCode
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/classes/util/Str.php:
--------------------------------------------------------------------------------
1 | [
17 |
18 | 'publish_redirects' => env('VDLP_REDIRECT_CRON_PUBLISH_REDIRECTS', '00:00'),
19 |
20 | ],
21 |
22 | /*
23 | |--------------------------------------------------------------------------
24 | | Logging
25 | |--------------------------------------------------------------------------
26 | |
27 | | Enable or disable specific logging information. Commonly used for
28 | | debugging purposes.
29 | |
30 | */
31 |
32 | 'log_redirect_changes' => (bool) env('VDLP_REDIRECT_LOG_REDIRECT_CHANGES', false),
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Redirect Rules Path
37 | |--------------------------------------------------------------------------
38 | |
39 | | The path of the redirect rules. Make sure the path is writable.
40 | |
41 | */
42 |
43 | 'rules_path' => env('VDLP_REDIRECT_RULES_PATH', storage_path('app/redirects.csv')),
44 |
45 | /*
46 | |--------------------------------------------------------------------------
47 | | Navigation
48 | |--------------------------------------------------------------------------
49 | */
50 |
51 | 'navigation' => [
52 | 'show_import' => env('VDLP_REDIRECT_SHOW_IMPORT', true),
53 | 'show_export' => env('VDLP_REDIRECT_SHOW_EXPORT', true),
54 | 'show_settings' => env('VDLP_REDIRECT_SHOW_SETTINGS', true),
55 | 'show_extensions' => env('VDLP_REDIRECT_SHOW_EXTENSION', true),
56 | ],
57 |
58 | ];
59 |
--------------------------------------------------------------------------------
/console/PublishRedirectsCommand.php:
--------------------------------------------------------------------------------
1 | signature = 'vdlp:redirect:publish-redirects';
15 | $this->description = 'Publish all redirects.';
16 |
17 | parent::__construct();
18 | }
19 |
20 | public function handle(PublishManager $publishManager): void
21 | {
22 | $publishManager->publish();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/controllers/Categories.php:
--------------------------------------------------------------------------------
1 | addCss('/plugins/vdlp/redirect/assets/css/redirect.css');
31 |
32 | NavigationManager::instance()->setContext('Vdlp.Redirect', 'redirect', 'categories');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/controllers/Extensions.php:
--------------------------------------------------------------------------------
1 | setContext('Vdlp.Redirect', 'redirect', 'extensions');
29 |
30 | $this->addCss('/plugins/vdlp/redirect/assets/css/redirect.css');
31 |
32 | $this->pageTitle = 'Redirect Extensions (new)';
33 | }
34 |
35 | public function index(): void
36 | {
37 | $this->vars['extensions'] = [];
38 |
39 | foreach (self::$extensions as $extension) {
40 | $this->vars['extensions'][$extension] = PluginManager::instance()->hasPlugin($extension)
41 | && !PluginManager::instance()->isDisabled($extension);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/controllers/Logs.php:
--------------------------------------------------------------------------------
1 | setContext('Vdlp.Redirect', 'redirect', 'logs');
40 |
41 | $this->addCss('/plugins/vdlp/redirect/assets/css/redirect.css');
42 | }
43 |
44 | public function onRefresh(): array
45 | {
46 | return $this->listRefresh();
47 | }
48 |
49 | public function onEmptyLog(): array
50 | {
51 | try {
52 | RedirectLog::query()->truncate();
53 | $this->flash->success($this->translator->trans('vdlp.redirect::lang.flash.truncate_success'));
54 | } catch (Throwable $e) {
55 | $this->log->warning($e);
56 | }
57 |
58 | return $this->listRefresh();
59 | }
60 |
61 | public function onDelete(): array
62 | {
63 | if (($checkedIds = $this->request->get('checked', []))
64 | && is_array($checkedIds)
65 | && count($checkedIds)
66 | ) {
67 | foreach ($checkedIds as $recordId) {
68 | try {
69 | /** @var RedirectLog $record */
70 | $record = RedirectLog::query()->findOrFail($recordId);
71 | $record->delete();
72 | } catch (Throwable $e) {
73 | $this->log->warning($e);
74 | }
75 | }
76 |
77 | $this->flash->success($this->translator->trans('vdlp.redirect::lang.flash.delete_selected_success'));
78 | } else {
79 | $this->flash->error($this->translator->trans('backend::lang.list.delete_selected_empty'));
80 | }
81 |
82 | return $this->listRefresh();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/controllers/Statistics.php:
--------------------------------------------------------------------------------
1 | setContext('Vdlp.Redirect', 'redirect', 'statistics');
29 |
30 | $this->pageTitle = 'vdlp.redirect::lang.title.statistics';
31 |
32 | $this->addCss('/plugins/vdlp/redirect/assets/css/redirect.css');
33 | $this->addCss('/plugins/vdlp/redirect/assets/css/statistics.css');
34 |
35 | $this->helper = new StatisticsHelper();
36 | }
37 |
38 | public function index(): void
39 | {
40 | }
41 |
42 | /**
43 | * @throws SystemException|JsonException|InvalidFormatException
44 | */
45 | public function onLoadHitsPerDay(): array
46 | {
47 | $today = Carbon::today();
48 |
49 | $postValue = post('period-month-year', $today->month . '_' . $today->year);
50 |
51 | [$month, $year] = explode('_', $postValue);
52 |
53 | return [
54 | '#hitsPerDay' => $this->makePartial('hits-per-day', [
55 | 'dataSets' => json_encode([
56 | $this->getHitsPerDayAsDataSet((int) $month, (int) $year, true),
57 | $this->getHitsPerDayAsDataSet((int) $month, (int) $year, false),
58 | ], JSON_THROW_ON_ERROR),
59 | 'labels' => json_encode($this->getLabels((int) $month, (int) $year), JSON_THROW_ON_ERROR),
60 | 'monthYearOptions' => $this->helper->getMonthYearOptions(),
61 | 'monthYearSelected' => $month . '_' . $year,
62 | ]),
63 | ];
64 | }
65 |
66 | /**
67 | * @throws InvalidFormatException
68 | * @throws JsonException
69 | * @throws SystemException
70 | */
71 | public function onSelectPeriodMonthYear(): array
72 | {
73 | return $this->onLoadHitsPerDay();
74 | }
75 |
76 | /**
77 | * @throws SystemException
78 | */
79 | public function onLoadTopRedirectsThisMonth(): array
80 | {
81 | return [
82 | '#topRedirectsThisMonth' => $this->makePartial('top-redirects-this-month', [
83 | 'topTenRedirectsThisMonth' => $this->helper->getTopRedirectsThisMonth(),
84 | ]),
85 | ];
86 | }
87 |
88 | /**
89 | * @throws SystemException
90 | */
91 | public function onLoadTopCrawlersThisMonth(): array
92 | {
93 | return [
94 | '#topCrawlersThisMonth' => $this->makePartial('top-crawlers-this-month', [
95 | 'topTenCrawlersThisMonth' => $this->helper->getTopTenCrawlersThisMonth(),
96 | ]),
97 | ];
98 | }
99 |
100 | /**
101 | * @throws SystemException
102 | */
103 | public function onLoadRedirectHitsPerMonth(): array
104 | {
105 | return [
106 | '#redirectHitsPerMonth' => $this->makePartial('redirect-hits-per-month', [
107 | 'redirectHitsPerMonth' => $this->helper->getRedirectHitsPerMonth(),
108 | ]),
109 | ];
110 | }
111 |
112 | /**
113 | * @throws SystemException
114 | */
115 | public function onLoadScoreBoard(): array
116 | {
117 | return [
118 | '#scoreBoard' => $this->makePartial('score-board', [
119 | 'redirectHitsPerMonth' => $this->helper->getRedirectHitsPerMonth(),
120 | 'totalActiveRedirects' => $this->helper->getTotalActiveRedirects(),
121 | 'activeRedirects' => $this->helper->getActiveRedirects(),
122 | 'totalRedirectsServed' => $this->helper->getTotalRedirectsServed(),
123 | 'totalThisMonth' => $this->helper->getTotalThisMonth(),
124 | 'totalLastMonth' => $this->helper->getTotalLastMonth(),
125 | 'latestClient' => $this->helper->getLatestClient(),
126 | ]),
127 | ];
128 | }
129 |
130 | private function getLabels(int $month, int $year): array
131 | {
132 | $labels = [];
133 |
134 | $dates = Carbon::create($year, $month)
135 | ->firstOfMonth()
136 | ->daysUntil(Carbon::create($year, $month)->endOfMonth());
137 |
138 | foreach ($dates as $date) {
139 | $labels[] = $date->isoFormat('LL');
140 | }
141 |
142 | return $labels;
143 | }
144 |
145 | /**
146 | * @throws InvalidFormatException
147 | */
148 | private function getHitsPerDayAsDataSet(int $month, int $year, bool $crawler): array
149 | {
150 | $today = Carbon::createFromDate($year, $month, 1);
151 |
152 | $data = $this->helper->getRedirectHitsPerDay($month, $year, $crawler);
153 |
154 | for ($i = $today->firstOfMonth()->day; $i <= $today->lastOfMonth()->day; $i++) {
155 | if (!array_key_exists($i, $data)) {
156 | $data[$i] = ['hits' => 0];
157 | }
158 | }
159 |
160 | ksort($data);
161 |
162 | $color = BrandHelper::instance()->getPrimaryOrSecondaryColor($crawler);
163 |
164 | [$r, $g, $b] = sscanf($color, "#%02x%02x%02x");
165 |
166 | return [
167 | 'label' => $crawler
168 | ? e(trans('vdlp.redirect::lang.statistics.crawler_hits'))
169 | : e(trans('vdlp.redirect::lang.statistics.visitor_hits')),
170 | 'backgroundColor' => sprintf('rgb(%d, %d, %d, 0.5)', $r, $g, $b),
171 | 'borderColor' => sprintf('rgb(%d, %d, %d, 1)', $r, $g, $b),
172 | 'borderWidth' => 1,
173 | 'data' => data_get($data, '*.hits'),
174 | ];
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/controllers/TestLab.php:
--------------------------------------------------------------------------------
1 | bodyClass = 'layout-relative';
33 |
34 | parent::__construct();
35 |
36 | NavigationManager::instance()->setContext('Vdlp.Redirect', 'redirect', 'test_lab');
37 |
38 | $this->loadRedirects();
39 | }
40 |
41 | public function index(): void
42 | {
43 | $this->pageTitle = 'vdlp.redirect::lang.title.test_lab';
44 |
45 | $this->addCss('/plugins/vdlp/redirect/assets/css/redirect.css');
46 | $this->addCss('/plugins/vdlp/redirect/assets/css/test-lab.css');
47 | $this->addJs('/plugins/vdlp/redirect/assets/javascript/test-lab.js');
48 |
49 | $this->vars['redirectCount'] = $this->getRedirectCount();
50 | }
51 |
52 | private function loadRedirects(): void
53 | {
54 | /** @var Collection $redirects */
55 | $this->redirects = array_values(Redirect::enabled()
56 | ->testLabEnabled()
57 | ->orderBy('sort_order')
58 | ->get()
59 | ->filter(static function (Redirect $redirect): bool {
60 | return $redirect->isActiveOnDate(Carbon::today());
61 | })
62 | ->all());
63 | }
64 |
65 | private function offsetGetRedirect(int $offset): ?Redirect
66 | {
67 | return $this->redirects[$offset] ?? null;
68 | }
69 |
70 | public function onTest(): string
71 | {
72 | $offset = (int) $this->request->get('offset');
73 |
74 | $redirect = $this->offsetGetRedirect($offset);
75 |
76 | if ($redirect === null) {
77 | return '';
78 | }
79 |
80 | try {
81 | $partial = (string) $this->makePartial('tester_result', [
82 | 'redirect' => $redirect,
83 | 'testPath' => $this->getTestPath($redirect),
84 | 'testResults' => $this->getTestResults($redirect, $this->request->secure()),
85 | ]);
86 | } catch (Throwable $e) {
87 | $partial = (string) $this->makePartial('tester_failed', [
88 | 'redirect' => $redirect,
89 | 'message' => $e->getMessage(),
90 | ]);
91 | }
92 |
93 | return $partial;
94 | }
95 |
96 | /**
97 | * @throws ModelNotFoundException
98 | */
99 | public function onReRun(): array
100 | {
101 | /** @var Redirect $redirect */
102 | $redirect = Redirect::query()->findOrFail($this->request->get('id'));
103 |
104 | $this->flash->success(trans('vdlp.redirect::lang.test_lab.flash_test_executed'));
105 |
106 | return [
107 | '#testerResult' . $redirect->getKey() => $this->makePartial(
108 | 'tester_result_items',
109 | $this->getTestResults($redirect, $this->request->secure())
110 | ),
111 | ];
112 | }
113 |
114 | /**
115 | * @throws ModelNotFoundException
116 | * @throws SystemException
117 | */
118 | public function onExclude(): array
119 | {
120 | /** @var Redirect $redirect */
121 | $redirect = Redirect::query()->findOrFail($this->request->get('id'));
122 | $redirect->update(['test_lab' => false]);
123 |
124 | $this->flash->success(trans('vdlp.redirect::lang.test_lab.flash_redirect_excluded'));
125 |
126 | return [
127 | '#testButtonWrapper' => $this->makePartial('test_button', [
128 | 'redirectCount' => $this->getRedirectCount(),
129 | ]),
130 | ];
131 | }
132 |
133 | public function getTestPath(Redirect $redirect): string
134 | {
135 | $testPath = '/';
136 |
137 | if ($redirect->isMatchTypeExact()) {
138 | $testPath = (string) $redirect->getAttribute('from_url');
139 | } elseif ($redirect->getAttribute('test_lab_path')) {
140 | $testPath = (string) $redirect->getAttribute('test_lab_path');
141 | }
142 |
143 | return $testPath;
144 | }
145 |
146 | public function getTestResults(Redirect $redirect, bool $secure): array
147 | {
148 | $testPath = $this->getTestPath($redirect);
149 |
150 | return [
151 | 'maxRedirectsResult' => (new Testers\RedirectLoop($testPath, $secure))->execute(),
152 | 'matchedRedirectResult' => (new Testers\RedirectMatch($testPath, $secure))->execute(),
153 | 'responseCodeResult' => (new Testers\ResponseCode($testPath, $secure))->execute(),
154 | 'redirectCountResult' => (new Testers\RedirectCount($testPath, $secure))->execute(),
155 | 'finalDestinationResult' => (new Testers\RedirectFinalDestination($testPath, $secure))->execute(),
156 | ];
157 | }
158 |
159 | private function getRedirectCount(): int
160 | {
161 | return count($this->redirects);
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/controllers/categories/_list_toolbar.htm:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/controllers/categories/config_form.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Form Behavior Config
3 | # ===================================
4 |
5 | # Record name
6 | name: vdlp.redirect::lang.redirect.category
7 |
8 | # Model Form Field configuration
9 | form: $/vdlp/redirect/models/category/fields.yaml
10 |
11 | # Model Class name
12 | modelClass: Vdlp\Redirect\Models\Category
13 |
14 | # Default redirect location
15 | defaultRedirect: vdlp/redirect/categories
16 |
17 | # Create page
18 | create:
19 | title: vdlp.redirect::lang.title.create_category
20 | redirect: vdlp/redirect/categories/update/:id
21 | redirectClose: vdlp/redirect/categories
22 |
23 | # Update page
24 | update:
25 | title: vdlp.redirect::lang.title.edit_category
26 | redirect: vdlp/redirect/categories
27 | redirectClose: vdlp/redirect/categories
28 |
--------------------------------------------------------------------------------
/controllers/categories/config_list.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Behavior Config
3 | # ===================================
4 |
5 | # Model List Column configuration
6 | list: $/vdlp/redirect/models/category/columns.yaml
7 |
8 | # Model Class name
9 | modelClass: Vdlp\Redirect\Models\Category
10 |
11 | # List Title
12 | title: vdlp.redirect::lang.title.categories
13 |
14 | # Link URL for each record
15 | recordUrl: vdlp/redirect/categories/update/:id
16 |
17 | # Message to display if the list is empty
18 | noRecordsMessage: backend::lang.list.no_records
19 |
20 | # Records to display per page
21 | recordsPerPage: 20
22 |
23 | # Displays the list column set up button
24 | # showSetup: true
25 |
26 | # Displays the sorting link on each column
27 | showSorting: true
28 |
29 | # Default sorting column
30 | # defaultSort:
31 | # column: created_at
32 | # direction: desc
33 |
34 | # Display checkboxes next to each record
35 | # showCheckboxes: true
36 |
37 | # Toolbar widget configuration
38 | toolbar:
39 | # Partial for toolbar buttons
40 | buttons: list_toolbar
41 |
42 | # Search widget configuration
43 | search:
44 | prompt: backend::lang.list.search_prompt
45 |
--------------------------------------------------------------------------------
/controllers/categories/create.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | fatalError): ?>
9 |
10 | = Form::open(['class' => 'layout']); ?>
11 |
12 |
13 | = $this->formRender(); ?>
14 |
15 |
16 |
40 |
41 | = Form::close(); ?>
42 |
43 |
44 |
45 | = e($this->fatalError); ?>
46 | = e(trans('vdlp.redirect::lang.redirect.return_to_categories')); ?>
47 |
48 |
49 |
--------------------------------------------------------------------------------
/controllers/categories/index.htm:
--------------------------------------------------------------------------------
1 |
2 | = $this->listRender(); ?>
3 |
--------------------------------------------------------------------------------
/controllers/categories/update.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | fatalError): ?>
9 |
10 | = Form::open(['class' => 'layout']); ?>
11 |
12 |
13 | = $this->formRender(); ?>
14 |
15 |
16 |
48 |
49 | = Form::close(); ?>
50 |
51 |
52 |
53 | = e($this->fatalError); ?>
54 | = e(trans('vdlp.redirect::lang.redirect.return_to_categories')); ?>
55 |
56 |
57 |
--------------------------------------------------------------------------------
/controllers/extensions/_installed.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 | installed
4 |
5 | not installed
6 |
7 |
8 |
--------------------------------------------------------------------------------
/controllers/logs/_list_toolbar.htm:
--------------------------------------------------------------------------------
1 |
29 |
--------------------------------------------------------------------------------
/controllers/logs/config_filter.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Filter Scope Definitions
3 | # ===================================
4 |
5 | scopes:
6 | updated_at:
7 | label: vdlp.redirect::lang.redirect.date
8 | type: date
9 | conditions: updated_at >= ':filtered'
10 | status_code:
11 | label: vdlp.redirect::lang.redirect.status_code
12 | type: group
13 | modelClass: Vdlp\Redirect\Models\Redirect
14 | options: filterStatusCodeOptions
15 | conditions: status_code in (:filtered)
16 |
--------------------------------------------------------------------------------
/controllers/logs/config_list.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Behavior Config
3 | # ===================================
4 |
5 | # Model List Column configuration
6 | list: $/vdlp/redirect/models/redirectlog/columns.yaml
7 |
8 | filter: config_filter.yaml
9 |
10 | # Model Class name
11 | modelClass: Vdlp\Redirect\Models\RedirectLog
12 |
13 | # List Title
14 | title: vdlp.redirect::lang.title.view_redirect_log
15 |
16 | # Link URL for each record
17 | recordUrl: vdlp/redirect/redirects/update/:redirect_id
18 |
19 | # Message to display if the list is empty
20 | noRecordsMessage: backend::lang.list.no_records
21 |
22 | # Records to display per page
23 | recordsPerPage: 20
24 |
25 | # Displays the list column set up button
26 | showSetup: true
27 |
28 | # Displays the sorting link on each column
29 | showSorting: true
30 |
31 | # Default sorting column
32 | defaultSort:
33 | column: updated_at
34 | direction: desc
35 |
36 | # Display checkboxes next to each record
37 | showCheckboxes: true
38 |
39 | # Toolbar widget configuration
40 | toolbar:
41 | # Partial for toolbar buttons
42 | buttons: list_toolbar
43 |
44 | # Search widget configuration
45 | search:
46 | prompt: backend::lang.list.search_prompt
47 |
--------------------------------------------------------------------------------
/controllers/logs/index.htm:
--------------------------------------------------------------------------------
1 |
2 | = $this->listRender(); ?>
3 |
--------------------------------------------------------------------------------
/controllers/redirects/_field_statistics.htm:
--------------------------------------------------------------------------------
1 | formGetModel();
3 | $redirectId = (int) $redirect->getKey();
4 | $latestClient = $statisticsHelper->getLatestClient($redirectId);
5 | ?>
6 |
7 |
8 |
9 |
10 |
= e(trans('vdlp.redirect::lang.statistics.activity_last_three_months')); ?>
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
= e(trans('vdlp.redirect::lang.statistics.redirects_this_month')); ?>
23 |
= number_format($statisticsHelper->getTotalThisMonth($redirectId), 0, '', '.') ?>
24 |
= e(trans('vdlp.redirect::lang.statistics.previous_month')) ?>: = number_format($statisticsHelper->getTotalLastMonth($redirectId), 0, '', '.') ?>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
= e(trans('vdlp.redirect::lang.redirect.hits')); ?>
33 |
= number_format($redirect->hits, 0, '', '.') ?>
34 |
35 |
36 |
37 |
38 |
39 | last_used_at, ['formatAlias' => 'dateTimeMin']) ?>
40 |
41 |
42 |
43 |
44 |
= e(trans('vdlp.redirect::lang.redirect.last_used_at')); ?>
45 |
= empty($lastUsedAt) ? '-' : $lastUsedAt; ?>
46 |
47 |
48 |
49 |
50 |
51 | updated_at, ['formatAlias' => 'dateTimeMin']) ?>
52 |
53 |
54 |
55 |
56 |
= e(trans('vdlp.redirect::lang.redirect.updated_at')); ?>
57 |
= empty($updatedAt) ? '-' : $updatedAt; ?>
58 |
59 |
60 |
61 |
62 |
63 | systemRequestLog): ?>
64 |
65 |
66 |
67 | = e(trans('vdlp.redirect::lang.redirect.created_due_to_bad_request')) ?>
68 |
69 |
70 |
--------------------------------------------------------------------------------
/controllers/redirects/_list_toolbar.htm:
--------------------------------------------------------------------------------
1 |
78 |
--------------------------------------------------------------------------------
/controllers/redirects/_popup_actions.htm:
--------------------------------------------------------------------------------
1 |
8 |
9 | getCacheManager()->cachingEnabledAndSupported()): ?>
10 |
11 |
14 | = e(trans('vdlp.redirect::lang.buttons.clear_cache')); ?>
15 |
16 |
17 | getCacheManager()->cachingEnabledButNotSupported()): ?>
18 |
19 |
21 | = e(trans('vdlp.redirect::lang.buttons.clear_cache')); ?>
22 |
23 |
24 |
25 |
26 |
29 | = e(trans('vdlp.redirect::lang.buttons.reset_all')); ?>
30 |
31 |
32 |
33 |
36 | = e(trans('vdlp.redirect::lang.buttons.enable')); ?> = e(trans('vdlp.redirect::lang.buttons.all_redirects')); ?>
37 |
38 |
39 |
40 |
43 | = e(trans('vdlp.redirect::lang.buttons.disable')); ?> = e(trans('vdlp.redirect::lang.buttons.all_redirects')); ?>
44 |
45 |
46 |
47 |
50 | = e(trans('vdlp.redirect::lang.buttons.delete')); ?> = e(trans('vdlp.redirect::lang.buttons.all_redirects')); ?>
51 |
52 |
53 |
54 |
60 |
--------------------------------------------------------------------------------
/controllers/redirects/_redirect_test.htm:
--------------------------------------------------------------------------------
1 |
30 |
31 | = $this->makePartial('redirect_test_result'); ?>
32 |
33 |
65 |
80 |
--------------------------------------------------------------------------------
/controllers/redirects/_redirect_test_result.htm:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/controllers/redirects/_reorder_toolbar.htm:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/controllers/redirects/_sparkline.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/controllers/redirects/_status_code_info.htm:
--------------------------------------------------------------------------------
1 |
5 |
6 |
50 |
--------------------------------------------------------------------------------
/controllers/redirects/_tab_logs.htm:
--------------------------------------------------------------------------------
1 | = $this->relationRender('logs') ?>
2 |
--------------------------------------------------------------------------------
/controllers/redirects/_warning.htm:
--------------------------------------------------------------------------------
1 |
2 | ×
6 |
11 |
12 |
--------------------------------------------------------------------------------
/controllers/redirects/config_filter.yaml:
--------------------------------------------------------------------------------
1 | scopes:
2 | system:
3 | label: vdlp.redirect::lang.list.switch_system
4 | type: switch
5 | conditions:
6 | - "`system` <> true"
7 | - "`system` = true"
8 | is_enabled:
9 | label: vdlp.redirect::lang.list.switch_is_enabled
10 | type: switch
11 | conditions:
12 | - is_enabled <> true
13 | - is_enabled = true
14 | match_type:
15 | label: vdlp.redirect::lang.redirect.match_type
16 | type: group
17 | modelClass: Vdlp\Redirect\Models\Redirect
18 | options: filterMatchTypeOptions
19 | conditions: match_type in (:filtered)
20 | target_type:
21 | label: vdlp.redirect::lang.redirect.target_type
22 | type: group
23 | modelClass: Vdlp\Redirect\Models\Redirect
24 | options: filterTargetTypeOptions
25 | conditions: target_type in (:filtered)
26 | status_code:
27 | label: vdlp.redirect::lang.redirect.status_code
28 | type: group
29 | modelClass: Vdlp\Redirect\Models\Redirect
30 | options: filterStatusCodeOptions
31 | conditions: status_code in (:filtered)
32 | category:
33 | label: vdlp.redirect::lang.redirect.category
34 | modelClass: Vdlp\Redirect\Models\Category
35 | conditions: category_id in (:filtered)
36 | nameFrom: name
37 | hits:
38 | label: vdlp.redirect::lang.redirect.has_hits
39 | type: switch
40 | conditions:
41 | - hits = 0
42 | - hits <> 0
43 | minimum_hits:
44 | label: vdlp.redirect::lang.redirect.minimum_hits
45 | type: number
46 | conditions: hits >= ':filtered'
47 |
--------------------------------------------------------------------------------
/controllers/redirects/config_form.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Form Behavior Config
3 | # ===================================
4 |
5 | # Record name
6 | name: vdlp.redirect::lang.redirect.redirect
7 |
8 | # Model Form Field configuration
9 | form: $/vdlp/redirect/models/redirect/fields.yaml
10 |
11 | # Model Class name
12 | modelClass: Vdlp\Redirect\Models\Redirect
13 |
14 | # Default redirect location
15 | defaultRedirect: vdlp/redirect/redirects
16 |
17 | # Create page
18 | create:
19 | title: vdlp.redirect::lang.title.create_redirect
20 | redirect: vdlp/redirect/redirects/update/:id
21 | redirectClose: vdlp/redirect/redirects
22 |
23 | # Update page
24 | update:
25 | title: vdlp.redirect::lang.title.edit_redirect
26 | redirect: vdlp/redirect/redirects
27 | redirectClose: vdlp/redirect/redirects
28 |
--------------------------------------------------------------------------------
/controllers/redirects/config_import_export.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Import/Export Behavior Config
3 | # ===================================
4 |
5 | import:
6 | title: vdlp.redirect::lang.title.import
7 | modelClass: Vdlp\Redirect\Models\RedirectImport
8 | list: $/vdlp/redirect/models/redirectimport/columns.yaml
9 | redirect: vdlp/redirect/redirects
10 | export:
11 | title: vdlp.redirect::lang.title.export
12 | modelClass: Vdlp\Redirect\Models\RedirectExport
13 | list: $/vdlp/redirect/models/redirectexport/columns.yaml
14 | redirect: vdlp/redirect/redirects
15 | filename: redirects.csv
16 |
--------------------------------------------------------------------------------
/controllers/redirects/config_list.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Behavior Config
3 | # ===================================
4 |
5 | # Model List Column configuration
6 | list: $/vdlp/redirect/models/redirect/columns.yaml
7 |
8 | # Model Class name
9 | modelClass: Vdlp\Redirect\Models\Redirect
10 |
11 | # List Title
12 | title: vdlp.redirect::lang.title.redirects
13 |
14 | # Link URL for each record
15 | recordUrl: vdlp/redirect/redirects/update/:id
16 |
17 | # Message to display if the list is empty
18 | noRecordsMessage: vdlp.redirect::lang.list.no_records
19 |
20 | # Records to display per page
21 | recordsPerPage: 20
22 |
23 | # Displays the list column set up button
24 | showSetup: true
25 |
26 | # Displays the sorting link on each column
27 | showSorting: true
28 |
29 | # Default sorting column
30 | defaultSort:
31 | column: sort_order
32 | direction: asc
33 |
34 | # Display checkboxes next to each record
35 | showCheckboxes: true
36 |
37 | # Toolbar widget configuration
38 | toolbar:
39 | # Partial for toolbar buttons
40 | buttons: list_toolbar
41 |
42 | # Search widget configuration
43 | search:
44 | prompt: backend::lang.list.search_prompt
45 |
46 | filter: config_filter.yaml
47 |
--------------------------------------------------------------------------------
/controllers/redirects/config_relation.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Relation Behavior Config
3 | # ===================================
4 |
5 | logs:
6 | label: Log
7 | view:
8 | list: $/vdlp/redirect/models/redirectlog/columns.yaml
9 | toolbarButtons: delete
10 | showSearch: true
11 | showSorting: true
12 | recordsPerPage: 10
13 | defaultSort: updated_at
14 |
--------------------------------------------------------------------------------
/controllers/redirects/config_reorder.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Reorder Behavior Config
3 | # ===================================
4 |
5 | # Reorder Title
6 | title: vdlp.redirect::lang.buttons.reorder_redirects
7 |
8 | # Attribute name
9 | nameFrom: from_url
10 |
11 | # Model Class name
12 | modelClass: Vdlp\Redirect\Models\Redirect
13 |
14 | # Toolbar widget configuration
15 | toolbar:
16 | # Partial for toolbar buttons
17 | buttons: reorder_toolbar
18 |
--------------------------------------------------------------------------------
/controllers/redirects/create.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | fatalError): ?>
9 |
10 | = Form::open(['class' => 'layout']); ?>
11 |
12 |
13 | = $this->formRender(); ?>
14 |
15 |
16 |
49 |
50 | = Form::close(); ?>
51 |
52 |
53 |
54 | = e($this->fatalError); ?>
55 | = e(trans('vdlp.redirect::lang.redirect.return_to_redirects')); ?>
56 |
57 |
58 |
--------------------------------------------------------------------------------
/controllers/redirects/export.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | = Form::open(['class' => 'layout']); ?>
9 |
10 | = $this->exportRender(); ?>
11 |
12 |
24 | = Form::close(); ?>
25 |
--------------------------------------------------------------------------------
/controllers/redirects/import.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | = Form::open(['class' => 'layout']); ?>
9 |
10 | = $this->importRender(); ?>
11 |
12 |
13 |
19 | = e(trans('vdlp.redirect::lang.buttons.import')); ?>
20 |
21 |
22 | = Form::close(); ?>
23 |
--------------------------------------------------------------------------------
/controllers/redirects/index.htm:
--------------------------------------------------------------------------------
1 |
2 | = $this->makePartial('warning', ['warningMessage' => $warningMessage]); ?>
3 |
4 |
5 | = $this->listRender(); ?>
6 |
--------------------------------------------------------------------------------
/controllers/redirects/reorder.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | = $this->reorderRender(); ?>
9 |
--------------------------------------------------------------------------------
/controllers/redirects/request-log/_list_toolbar.htm:
--------------------------------------------------------------------------------
1 |
14 |
24 |
25 |
35 | = e(trans('vdlp.redirect::lang.buttons.create_redirects')); ?>
36 |
37 |
38 |
--------------------------------------------------------------------------------
/controllers/redirects/request-log/_modal.htm:
--------------------------------------------------------------------------------
1 |
5 |
6 | = $this->listRender('requestLog'); ?>
7 |
8 |
--------------------------------------------------------------------------------
/controllers/redirects/request-log/columns.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Column Definitions
3 | # ===================================
4 |
5 | columns:
6 | url:
7 | label: system::lang.request_log.url
8 | searchable: yes
9 | cssClass: column-break-word
10 |
11 | status_code:
12 | label: system::lang.request_log.status_code
13 | searchable: yes
14 | width: 100px
15 |
16 | count:
17 | label: system::lang.request_log.count
18 | width: 150px
19 |
--------------------------------------------------------------------------------
/controllers/redirects/request-log/config_list.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Behavior Config
3 | # ===================================
4 |
5 | title: system::lang.request_log.menu_label
6 | list: $/vdlp/redirect/controllers/redirects/request-log/columns.yaml
7 | modelClass: System\Models\RequestLog
8 | noRecordsMessage: backend::lang.list.no_records
9 | recordsPerPage: 10
10 | showSetup: true
11 | showCheckboxes: true
12 | defaultSort:
13 | column: count
14 | direction: desc
15 |
16 | toolbar:
17 | buttons: request-log/list_toolbar
18 | search:
19 | prompt: backend::lang.list.search_prompt
20 |
--------------------------------------------------------------------------------
/controllers/redirects/update.htm:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | fatalError): ?>
9 |
10 |
11 |
12 |
13 |
14 |
15 | = $this->makePartial('warning', ['warningMessage' => $warningMessage]); ?>
16 |
17 | = $this->formRenderOutsideFields() ?>
18 | = $this->formRenderPrimaryTabs() ?>
19 |
20 |
21 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | = $this->formRenderSecondaryTabs() ?>
60 |
61 |
62 |
63 |
64 | = Form::open(['class'=>'layout stretch']) ?>
65 | = $this->makeLayout('form-with-sidebar') ?>
66 | = Form::close() ?>
67 |
68 |
69 |
70 |
71 |
72 | = Block::placeholder('breadcrumb') ?>
73 |
74 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/controllers/statistics/_hits-per-day.htm:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
= e(trans('vdlp.redirect::lang.statistics.hits_per_day')); ?>
33 |
34 |
35 |
36 |
37 | Period
38 |
39 |
44 | $label): ?>
45 |
46 | >= $label ?>
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/controllers/statistics/_loading-indicator.htm:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/controllers/statistics/_redirect-hits-per-month.htm:
--------------------------------------------------------------------------------
1 | = e(trans('vdlp.redirect::lang.statistics.redirects_per_month')); ?>
2 |
3 |
4 |
5 | $record): ?>
6 |
7 | = $key + 1 ?>
8 | = date('F', mktime(0, 0, 0, (int) $record['month'])); ?> = e($record['year']) ?>
9 | = number_format($record['hits'], 0, null, '.'); ?>
10 |
11 |
12 |
13 |
14 |
15 | = e(trans('vdlp.redirect::lang.statistics.no_data')); ?>
16 |
17 |
--------------------------------------------------------------------------------
/controllers/statistics/_score-board.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 | $activeRedirect): ?>
9 | = e(trans('vdlp.redirect::lang.redirect.' . \Vdlp\Redirect\Models\Redirect::$statusCodes[$group])); ?>
10 | = count($activeRedirect); ?>
11 |
12 |
13 |
14 |
15 |
= e(trans('vdlp.redirect::lang.statistics.requests_redirected')); ?>
16 |
= number_format($totalRedirectsServed, 0, '', '.'); ?>
17 |
= e(trans('vdlp.redirect::lang.statistics.all_time')); ?>
18 |
19 |
20 |
= e(trans('vdlp.redirect::lang.statistics.active_redirects')); ?>
21 |
= number_format($totalActiveRedirects, 0, '', '.'); ?>
22 |
23 |
24 |
= e(trans('vdlp.redirect::lang.statistics.redirects_this_month')); ?>
25 |
= number_format($totalThisMonth, 0, '', '.'); ?>
26 |
= e(trans('vdlp.redirect::lang.statistics.previous_month')); ?>: = number_format($totalLastMonth, 0, '', '.'); ?>
27 |
28 |
29 |
30 |
= e(trans('vdlp.redirect::lang.statistics.latest_redirected_requests')); ?>
31 |
= e($latestClient->redirect->from_url) ?>
32 |
= e($latestClient->timestamp) ?>
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/controllers/statistics/_top-crawlers-this-month.htm:
--------------------------------------------------------------------------------
1 | = e(trans('vdlp.redirect::lang.statistics.top_crawlers_this_month', ['top' => 10])); ?>
2 |
3 |
4 |
5 | $record): ?>
6 |
7 | = $key + 1 ?>
8 | = e($record['crawler']) ?>
9 | = number_format($record['hits'], 0, null, '.'); ?>
10 |
11 |
12 |
13 |
14 |
15 | = e(trans('vdlp.redirect::lang.statistics.no_data')); ?>
16 |
17 |
--------------------------------------------------------------------------------
/controllers/statistics/_top-redirects-this-month.htm:
--------------------------------------------------------------------------------
1 | = e(trans('vdlp.redirect::lang.statistics.top_redirects_this_month', ['top' => 10])); ?>
2 |
3 |
18 |
19 | = e(trans('vdlp.redirect::lang.statistics.no_data')); ?>
20 |
21 |
--------------------------------------------------------------------------------
/controllers/statistics/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | = $this->makePartial('loading-indicator'); ?>
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | = $this->makePartial('loading-indicator'); ?>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | = $this->makePartial('loading-indicator'); ?>
31 |
32 |
33 |
34 |
35 |
36 | = $this->makePartial('loading-indicator'); ?>
37 |
38 |
39 |
40 |
41 |
42 | = $this->makePartial('loading-indicator'); ?>
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/controllers/testlab/_test_button.htm:
--------------------------------------------------------------------------------
1 |
7 | = e(trans('vdlp.redirect::lang.test_lab.start_tests')); ?> (= $redirectCount ?>)
8 |
9 |
--------------------------------------------------------------------------------
/controllers/testlab/_tester_failed.htm:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/controllers/testlab/_tester_result.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 | = e($testPath) ?> to_url)): ?>
4 |
5 | = e($redirect->to_url) ?>
6 |
7 |
8 |
33 |
34 |
35 | = $this->makePartial('tester_result_items', $testResults); ?>
36 |
37 |
--------------------------------------------------------------------------------
/controllers/testlab/_tester_result_items.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1. = e(trans('vdlp.redirect::lang.test_lab.loop')); ?>
5 |
6 |
= $maxRedirectsResult->getMessage(); ?>
7 |
= $maxRedirectsResult->getDuration(); ?> ms
8 |
9 |
10 |
11 |
12 | 2. = e(trans('vdlp.redirect::lang.test_lab.match')); ?>
13 |
14 |
= $matchedRedirectResult->getMessage(); ?>
15 |
= $matchedRedirectResult->getDuration(); ?> ms
16 |
17 |
18 |
19 |
20 | 3. = e(trans('vdlp.redirect::lang.test_lab.response_http_code')); ?>
21 |
22 |
= $responseCodeResult->getMessage(); ?>
23 |
= $responseCodeResult->getDuration(); ?> ms
24 |
25 |
26 |
27 |
28 | 4. = e(trans('vdlp.redirect::lang.test_lab.redirect_count')); ?>
29 |
30 |
= $redirectCountResult->getMessage(); ?>
31 |
= $redirectCountResult->getDuration(); ?> ms
32 |
33 |
34 |
35 |
36 | 5. = e(trans('vdlp.redirect::lang.test_lab.final_destination')); ?>
37 |
38 |
= $finalDestinationResult->getMessage(); ?>
39 |
= $finalDestinationResult->getDuration(); ?> ms
40 |
41 |
--------------------------------------------------------------------------------
/controllers/testlab/index.htm:
--------------------------------------------------------------------------------
1 | = Form::open(['class' => 'layout']); ?>
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
19 | = e(trans('vdlp.redirect::lang.test_lab.start_tests_description')); ?>
20 |
21 |
22 |
23 | = $this->makePartial('test_button', ['redirectCount' => $redirectCount]); ?>
24 |
25 |
26 |
27 |
28 |
29 |
30 |
×
34 |
35 |
36 |
= e(trans('vdlp.redirect::lang.test_lab.no_redirects')); ?>
37 |
38 |
39 |
40 |
41 |
42 |
43 | = Form::close(); ?>
44 |
45 |
53 |
--------------------------------------------------------------------------------
/models/Category.php:
--------------------------------------------------------------------------------
1 | Redirect::class,
13 | ];
14 |
15 | public $timestamps = false;
16 |
17 | protected $table = 'vdlp_redirect_clients';
18 |
19 | protected $guarded = [];
20 | }
21 |
--------------------------------------------------------------------------------
/models/RedirectExport.php:
--------------------------------------------------------------------------------
1 | get()
17 | ->toArray();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/models/RedirectImport.php:
--------------------------------------------------------------------------------
1 | 'required',
19 | 'match_type' => 'required|in:exact,placeholders,regex',
20 | 'target_type' => 'required|in:path_or_url,cms_page,static_page,none',
21 | 'status_code' => 'required|in:301,302,303,404,410',
22 | ];
23 |
24 | private static array $nullableAttributes = [
25 | 'category_id',
26 | 'from_date',
27 | 'to_date',
28 | 'last_used_at',
29 | 'to_url',
30 | 'test_url',
31 | 'cms_page',
32 | 'static_page',
33 | 'requirements',
34 | 'test_lab_path',
35 | ];
36 |
37 | protected $table = 'vdlp_redirect_redirects';
38 |
39 | public function importData($results, $sessionKey = null)
40 | {
41 | foreach ((array) $results as $row => $data) {
42 | try {
43 | $source = Redirect::make();
44 |
45 | $except = ['id'];
46 |
47 | foreach (array_except($data, $except) as $attribute => $value) {
48 | if ($attribute === 'requirements') {
49 | $value = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
50 | } elseif (empty($value) && in_array($attribute, self::$nullableAttributes, true)) {
51 | $value = null;
52 | }
53 |
54 | $source->setAttribute($attribute, $value);
55 | }
56 |
57 | $source->save();
58 |
59 | $this->logCreated();
60 | } catch (Throwable $e) {
61 | $this->logError($row, $e->getMessage());
62 | }
63 | }
64 |
65 | $createCount = $this->resultStats['created'] ?? 0;
66 |
67 | if ($createCount === 0) {
68 | return;
69 | }
70 |
71 | /** @var PublishManager $publishManager */
72 | $publishManager = resolve(PublishManager::class);
73 | $publishManager->publish();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/models/RedirectLog.php:
--------------------------------------------------------------------------------
1 | Redirect::class,
13 | ];
14 |
15 | protected $table = 'vdlp_redirect_redirect_logs';
16 |
17 | protected $guarded = [];
18 | }
19 |
--------------------------------------------------------------------------------
/models/Settings.php:
--------------------------------------------------------------------------------
1 | implement = [SettingsModel::class];
30 |
31 | parent::__construct($attributes);
32 | }
33 |
34 | public static function isLoggingEnabled(): bool
35 | {
36 | try {
37 | return (bool) (new self())->get('logging_enabled', false);
38 | } catch (Throwable) {
39 | return false;
40 | }
41 | }
42 |
43 | public static function isStatisticsEnabled(): bool
44 | {
45 | try {
46 | return (bool) (new self())->get('statistics_enabled', false);
47 | } catch (Throwable) {
48 | return false;
49 | }
50 | }
51 |
52 | public static function isTestLabEnabled(): bool
53 | {
54 | try {
55 | return (bool) (new self())->get('test_lab_enabled', false);
56 | } catch (Throwable) {
57 | return false;
58 | }
59 | }
60 |
61 | public static function isCachingEnabled(): bool
62 | {
63 | try {
64 | return (bool) (new self())->get('caching_enabled', false);
65 | } catch (Throwable) {
66 | return false;
67 | }
68 | }
69 |
70 | public static function isRelativePathsEnabled(): bool
71 | {
72 | try {
73 | return (bool) (new self())->get('relative_paths_enabled', true);
74 | } catch (Throwable) {
75 | return true;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/models/category/columns.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Column Definitions
3 | # ===================================
4 |
5 | columns:
6 | name:
7 | label: vdlp.redirect::lang.redirect.name
8 | searchable: true
9 |
--------------------------------------------------------------------------------
/models/category/fields.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Form Field Definitions
3 | # ===================================
4 |
5 | fields:
6 | name:
7 | label: vdlp.redirect::lang.redirect.name
8 | span: left
9 |
--------------------------------------------------------------------------------
/models/client/columns.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Column Definitions
3 | # ===================================
4 |
5 | columns:
6 | id:
7 | label: ID
8 | searchable: true
--------------------------------------------------------------------------------
/models/client/fields.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Form Field Definitions
3 | # ===================================
4 |
5 | fields:
6 | id:
7 | label: ID
8 | disabled: true
9 |
--------------------------------------------------------------------------------
/models/redirect/columns.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Column Definitions
3 | # ===================================
4 |
5 | columns:
6 | from_url:
7 | label: vdlp.redirect::lang.redirect.from_url
8 | type: redirect_from_url
9 | searchable: true
10 | target_type:
11 | label: vdlp.redirect::lang.redirect.target_type
12 | type: redirect_target_type
13 | status_code:
14 | label: vdlp.redirect::lang.redirect.status_code
15 | type: redirect_status_code
16 | match_type:
17 | label: vdlp.redirect::lang.redirect.match_type
18 | type: redirect_match_type
19 | category:
20 | label: vdlp.redirect::lang.redirect.category
21 | relation: category
22 | select: name
23 | default: '-'
24 | description:
25 | label: vdlp.redirect::lang.redirect.description
26 | invisible: true
27 | default: '-'
28 | hits:
29 | label: vdlp.redirect::lang.redirect.hits
30 | type: number
31 | last_used_at:
32 | label: vdlp.redirect::lang.redirect.last_used_at
33 | type: datetime
34 | sparkline:
35 | label: vdlp.redirect::lang.redirect.sparkline_30d
36 | sortable: false
37 | searchable: false
38 | cssClass: column-button
39 | type: partial
40 | sort_order:
41 | label: vdlp.redirect::lang.redirect.priority
42 | is_enabled:
43 | label: vdlp.redirect::lang.redirect.enabled
44 | type: redirect_switch_color
45 | system:
46 | label: vdlp.redirect::lang.redirect.type
47 | width: 16px
48 | type: redirect_system
49 | sortable: false
50 | invisible: true
51 | updated_at:
52 | label: vdlp.redirect::lang.redirect.modified_at
53 | type: datetime
54 | invisible: true
55 | created_at:
56 | label: vdlp.redirect::lang.redirect.created_at
57 | type: datetime
58 | invisible: true
59 |
--------------------------------------------------------------------------------
/models/redirectexport/columns.yaml:
--------------------------------------------------------------------------------
1 | columns:
2 | id: ID
3 | match_type: vdlp.redirect::lang.import_export.match_type
4 | category_id: vdlp.redirect::lang.import_export.category_id
5 | target_type: vdlp.redirect::lang.import_export.target_type
6 | from_url: vdlp.redirect::lang.import_export.from_url
7 | from_scheme: vdlp.redirect::lang.import_export.from_scheme
8 | to_url: vdlp.redirect::lang.import_export.to_url
9 | to_scheme: vdlp.redirect::lang.import_export.to_scheme
10 | test_url: vdlp.redirect::lang.import_export.test_url
11 | cms_page: vdlp.redirect::lang.import_export.cms_page
12 | static_page: vdlp.redirect::lang.import_export.static_page
13 | requirements: vdlp.redirect::lang.import_export.requirements
14 | status_code: vdlp.redirect::lang.import_export.status_code
15 | hits: vdlp.redirect::lang.import_export.hits
16 | from_date: vdlp.redirect::lang.import_export.from_date
17 | to_date: vdlp.redirect::lang.import_export.to_date
18 | sort_order: vdlp.redirect::lang.import_export.sort_order
19 | ignore_query_parameters: vdlp.redirect::lang.import_export.ignore_query_parameters
20 | ignore_case: vdlp.redirect::lang.import_export.ignore_case
21 | ignore_trailing_slash: vdlp.redirect::lang.import_export.ignore_trailing_slash
22 | is_enabled: vdlp.redirect::lang.import_export.is_enabled
23 | test_lab: vdlp.redirect::lang.import_export.test_lab
24 | test_lab_path: vdlp.redirect::lang.import_export.test_lab_path
25 | system: vdlp.redirect::lang.import_export.system
26 | description: vdlp.redirect::lang.import_export.description
27 | last_used_at: vdlp.redirect::lang.import_export.last_used_at
28 | created_at: vdlp.redirect::lang.import_export.created_at
29 | updated_at: vdlp.redirect::lang.import_export.updated_at
30 |
--------------------------------------------------------------------------------
/models/redirectimport/columns.yaml:
--------------------------------------------------------------------------------
1 | columns:
2 | id: ID
3 | match_type: vdlp.redirect::lang.import_export.match_type
4 | category_id: vdlp.redirect::lang.import_export.category_id
5 | target_type: vdlp.redirect::lang.import_export.target_type
6 | from_url: vdlp.redirect::lang.import_export.from_url
7 | from_scheme: vdlp.redirect::lang.import_export.from_scheme
8 | to_url: vdlp.redirect::lang.import_export.to_url
9 | to_scheme: vdlp.redirect::lang.import_export.to_scheme
10 | test_url: vdlp.redirect::lang.import_export.test_url
11 | cms_page: vdlp.redirect::lang.import_export.cms_page
12 | static_page: vdlp.redirect::lang.import_export.static_page
13 | requirements: vdlp.redirect::lang.import_export.requirements
14 | status_code: vdlp.redirect::lang.import_export.status_code
15 | hits: vdlp.redirect::lang.import_export.hits
16 | from_date: vdlp.redirect::lang.import_export.from_date
17 | to_date: vdlp.redirect::lang.import_export.to_date
18 | sort_order: vdlp.redirect::lang.import_export.sort_order
19 | ignore_query_parameters: vdlp.redirect::lang.import_export.ignore_query_parameters
20 | ignore_case: vdlp.redirect::lang.import_export.ignore_case
21 | ignore_trailing_slash: vdlp.redirect::lang.import_export.ignore_trailing_slash
22 | is_enabled: vdlp.redirect::lang.import_export.is_enabled
23 | test_lab: vdlp.redirect::lang.import_export.test_lab
24 | test_lab_path: vdlp.redirect::lang.import_export.test_lab_path
25 | system: vdlp.redirect::lang.import_export.system
26 | description: vdlp.redirect::lang.import_export.description
27 | last_used_at: vdlp.redirect::lang.import_export.last_used_at
28 | created_at: vdlp.redirect::lang.import_export.created_at
29 | updated_at: vdlp.redirect::lang.import_export.updated_at
30 |
--------------------------------------------------------------------------------
/models/redirectlog/columns.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # List Column Definitions
3 | # ===================================
4 |
5 | columns:
6 | from_url:
7 | label: vdlp.redirect::lang.redirect.from_url
8 | type: redirect_from_url
9 | searchable: true
10 | to_url:
11 | label: vdlp.redirect::lang.redirect.to_url
12 | searchable: true
13 | status_code:
14 | label: vdlp.redirect::lang.redirect.status_code
15 | type: redirect_status_code
16 | searchable: true
17 | hits:
18 | label: vdlp.redirect::lang.redirect.hits
19 | updated_at:
20 | label: vdlp.redirect::lang.redirect.date_time
21 | type: datetime
22 |
--------------------------------------------------------------------------------
/models/settings/fields.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Settings Form Field Definitions
3 | # ===================================
4 |
5 | fields:
6 | redirect_settings_section:
7 | label: vdlp.redirect::lang.settings.menu_label
8 | comment: vdlp.redirect::lang.settings.menu_description
9 | type: section
10 | span: left
11 | relative_paths_enabled:
12 | label: vdlp.redirect::lang.settings.relative_paths_enabled_label
13 | comment: vdlp.redirect::lang.settings.relative_paths_enabled_command
14 | span: left
15 | type: switch
16 | default: true
17 | logging_enabled:
18 | label: vdlp.redirect::lang.settings.logging_enabled_label
19 | comment: vdlp.redirect::lang.settings.logging_enabled_comment
20 | span: left
21 | type: switch
22 | default: false
23 | statistics_enabled:
24 | label: vdlp.redirect::lang.settings.statistics_enabled_label
25 | comment: vdlp.redirect::lang.settings.statistics_enabled_comment
26 | span: left
27 | type: switch
28 | default: false
29 | test_lab_enabled:
30 | label: vdlp.redirect::lang.settings.test_lab_enabled_label
31 | comment: vdlp.redirect::lang.settings.test_lab_enabled_comment
32 | span: left
33 | type: switch
34 | default: false
35 | caching_enabled:
36 | label: vdlp.redirect::lang.settings.caching_enabled_label
37 | comment: vdlp.redirect::lang.settings.caching_enabled_comment
38 | span: left
39 | type: switch
40 | default: false
41 |
42 |
--------------------------------------------------------------------------------
/reportwidgets/CreateRedirect.php:
--------------------------------------------------------------------------------
1 | alias = 'redirectCreateRedirect';
25 |
26 | parent::__construct($controller, $properties);
27 |
28 | $this->redirect = resolve(Redirector::class);
29 | }
30 |
31 | /**
32 | * @noinspection PhpMissingParentCallCommonInspection
33 | */
34 | public function render()
35 | {
36 | $widgetConfig = $this->makeConfig('~/plugins/vdlp/redirect/reportwidgets/createredirect/fields.yaml');
37 | $widgetConfig->model = new Redirect;
38 | $widgetConfig->alias = $this->alias . 'Redirect';
39 |
40 | $this->vars['formWidget'] = $this->makeWidget(Form::class, $widgetConfig);
41 |
42 | return $this->makePartial('widget');
43 | }
44 |
45 | public function onSubmit(): RedirectResponse
46 | {
47 | $redirect = Redirect::create([
48 | 'match_type' => Redirect::TYPE_EXACT,
49 | 'target_type' => Redirect::TARGET_TYPE_PATH_URL,
50 | 'from_url' => post('from_url'),
51 | 'from_scheme' => Redirect::SCHEME_AUTO,
52 | 'to_url' => post('to_url'),
53 | 'to_scheme' => Redirect::SCHEME_AUTO,
54 | 'test_url' => post('from_url'),
55 | 'requirements' => null,
56 | 'status_code' => 302,
57 | ]);
58 |
59 | return $this->redirect->to(Backend::url('vdlp/redirect/redirects/update/' . $redirect->getKey()));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/reportwidgets/TopTenRedirects.php:
--------------------------------------------------------------------------------
1 | alias = 'redirectTopTenRedirects';
19 |
20 | parent::__construct($controller, $properties);
21 | }
22 |
23 | /**
24 | * @noinspection PhpMissingParentCallCommonInspection
25 | */
26 | public function render()
27 | {
28 | $helper = new StatisticsHelper();
29 |
30 | return $this->makePartial('widget', [
31 | 'topTenRedirectsThisMonth' => $helper->getTopRedirectsThisMonth(),
32 | ]);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/reportwidgets/createredirect/fields.yaml:
--------------------------------------------------------------------------------
1 | # ===================================
2 | # Form Field Definitions
3 | # ===================================
4 |
5 | fields:
6 | from_url:
7 | label: vdlp.redirect::lang.redirect.from_url
8 | placeholder: vdlp.redirect::lang.redirect.from_url_placeholder
9 | type: text
10 | span: left
11 | comment: vdlp.redirect::lang.redirect.from_url_comment
12 | required: true
13 | attributes:
14 | autofocus: ''
15 | to_url:
16 | label: vdlp.redirect::lang.redirect.to_url
17 | placeholder: vdlp.redirect::lang.redirect.to_url_placeholder
18 | type: text
19 | span: right
20 | comment: vdlp.redirect::lang.redirect.to_url_comment
21 |
--------------------------------------------------------------------------------
/reportwidgets/createredirect/partials/_widget.htm:
--------------------------------------------------------------------------------
1 |
2 |
= e(trans('vdlp.redirect::lang.buttons.create_redirect')) ?>
3 | = Form::open() ?>
4 | = $formWidget->render() ?>
5 |
8 | = e(trans('vdlp.redirect::lang.buttons.create_redirect')) ?>
9 |
10 | = Form::close() ?>
11 |
12 |
--------------------------------------------------------------------------------
/reportwidgets/toptenredirects/partials/_widget.htm:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/routes.php:
--------------------------------------------------------------------------------
1 | ['web']], static function (): void {
14 | Route::get('vdlp/redirect/sparkline/{redirectId}', static function (Request $request, $redirectId) {
15 | if (!BackendAuth::check()) {
16 | return response('Forbidden', 403);
17 | }
18 |
19 | $crawler = $request->has('crawler');
20 |
21 | $preset = $request->get('preset', '30d-small');
22 |
23 | $properties = [
24 | 'format' => '200x60',
25 | 'lineThickness' => 3,
26 | 'days' => 30,
27 | ];
28 |
29 | if ($preset === '3m-large') {
30 | $properties = [
31 | 'format' => '520x120',
32 | 'lineThickness' => 2,
33 | 'days' => 90,
34 | ];
35 | }
36 |
37 | $cacheKey = sprintf('vdlp_redirect_%s_%d_%d', $preset, (int) $redirectId, (int) $crawler);
38 |
39 | $data = Cache::remember($cacheKey, 5 * 60, static function () use ($redirectId, $crawler, $properties) {
40 | return (new StatisticsHelper())->getRedirectHitsSparkline((int) $redirectId, $crawler, $properties['days']);
41 | });
42 |
43 | // TODO: Generate fallback image data if generating image fails.
44 | $imageData = Cache::remember($cacheKey . '_image', 5 * 60, static function () use ($crawler, $data, $properties) {
45 | $primaryColor = BrandHelper::instance()
46 | ->getPrimaryOrSecondaryColor($crawler);
47 |
48 | $sparkline = new Sparkline();
49 | $sparkline->setFormat($properties['format']);
50 | $sparkline->setPadding('2 0 0 2');
51 | $sparkline->setData($data);
52 | $sparkline->setLineThickness($properties['lineThickness']);
53 | $sparkline->setLineColorHex($primaryColor);
54 | $sparkline->setFillColorHex($primaryColor);
55 | $sparkline->deactivateBackgroundColor();
56 |
57 | return $sparkline->toBase64();
58 | });
59 |
60 | // TODO: Leverage Browser Caching
61 | header('Content-Type: image/png');
62 | header('Content-Disposition: inline; filename="' . $cacheKey . '.png"');
63 | header('Accept-Ranges: none');
64 |
65 | echo base64_decode($imageData, true);
66 |
67 | exit(0);
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/updates/20180831_0002_upgrade_from_adrenth_redirect.php:
--------------------------------------------------------------------------------
1 | getSchemaBuilder();
27 |
28 | if (!$schema->hasTable('adrenth_redirect_redirects')) {
29 | // Skip upgrade migration.
30 | $log->info('No upgrade of Vdlp.Redirect needed. Fresh installation.');
31 |
32 | return;
33 | }
34 |
35 | try {
36 | $database->transaction(function () use ($database): void {
37 | $this->disableForeignKeyCheck($database);
38 |
39 | $mapping = [
40 | 'adrenth_redirect_categories' => 'vdlp_redirect_categories',
41 | 'adrenth_redirect_redirects' => 'vdlp_redirect_redirects',
42 | 'adrenth_redirect_redirect_logs' => 'vdlp_redirect_redirect_logs',
43 | 'adrenth_redirect_clients' => 'vdlp_redirect_clients',
44 | ];
45 |
46 | // language=ignore
47 | foreach ($mapping as $from => $to) {
48 | // Make sure newly created tables are empty.
49 | $database->table($to)->delete();
50 |
51 | // Move data from old tables to new ones.
52 | $database->statement("INSERT INTO `$to` SELECT * FROM `$from`;");
53 | }
54 |
55 | // Migrate plugin settings.
56 | $database->table('system_settings')
57 | ->where('item', '=', 'vdlp_redirect_settings')
58 | ->delete();
59 |
60 | // language=ignore
61 | $database->statement(
62 | 'INSERT INTO `system_settings` '
63 | . "SELECT NULL, 'vdlp_redirect_settings', `value` "
64 | . 'FROM `system_settings` '
65 | . "WHERE `item` = 'adrenth_redirect_settings';"
66 | );
67 |
68 | $this->enableForeignKeyCheck($database);
69 | });
70 | } catch (Throwable $e) {
71 | $log->error(sprintf(
72 | 'Vdlp.Redirect: Could not upgrade plugin Vdlp.Redirect from Adrenth.Redirect: %s',
73 | $e->getMessage()
74 | ));
75 | }
76 | }
77 |
78 | public function down(): void
79 | {
80 | // No migrations to reverse.
81 | }
82 |
83 | private function disableForeignKeyCheck(DatabaseManager $database): void
84 | {
85 | if ($database->getDriverName() === 'sqlite') {
86 | $database->raw('PRAGMA foreign_keys = OFF;');
87 | }
88 |
89 | if ($database->getDriverName() === 'mysql') {
90 | $database->raw('SET FOREIGN_KEY_CHECKS = 0;');
91 | }
92 |
93 | if ($database->getDriverName() === 'pgsql') {
94 | $database->raw('SET CONSTRAINTS ALL DEFERRED;');
95 | }
96 | }
97 |
98 | private function enableForeignKeyCheck(DatabaseManager $database): void
99 | {
100 | if ($database->getDriverName() === 'sqlite') {
101 | $database->raw('PRAGMA foreign_keys = ON;');
102 | }
103 |
104 | if ($database->getDriverName() === 'mysql') {
105 | $database->raw('SET FOREIGN_KEY_CHECKS = 1;');
106 | }
107 |
108 | if ($database->getDriverName() === 'pgsql') {
109 | $database->raw('PRAGMA foreign_keys = ON;');
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/updates/20181019_0003_add_ignore_query_parameters_to_redirects_table.php:
--------------------------------------------------------------------------------
1 | boolean('ignore_query_parameters')
22 | ->default(false)
23 | ->after('sort_order');
24 | });
25 | }
26 |
27 | public function down(): void
28 | {
29 | try {
30 | Schema::table('vdlp_redirect_redirects', static function (Blueprint $table): void {
31 | $table->dropColumn('ignore_query_parameters');
32 | });
33 | } catch (Throwable $e) {
34 | /** @var LoggerInterface $logger */
35 | $logger = resolve(LoggerInterface::class);
36 | $logger->error(sprintf(
37 | 'Vdlp.Redirect: Unable to drop column `%s` from table `%s`: %s',
38 | 'ignore_query_parameters',
39 | 'vdlp_redirect_redirects',
40 | $e->getMessage()
41 | ));
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/updates/20181117_0004_add_redirect_timestamp_crawler_index_on_clients_table.php:
--------------------------------------------------------------------------------
1 | index(
19 | [
20 | 'redirect_id',
21 | 'timestamp',
22 | 'crawler',
23 | ],
24 | 'redirect_timestamp_crawler'
25 | );
26 | });
27 | }
28 |
29 | public function down(): void
30 | {
31 | try {
32 | Schema::table('vdlp_redirect_clients', static function (Blueprint $table): void {
33 | $table->dropIndex('redirect_timestamp_crawler');
34 | });
35 | } catch (Throwable $e) {
36 | /** @var LoggerInterface $logger */
37 | $logger = resolve(LoggerInterface::class);
38 | $logger->error(sprintf(
39 | 'Vdlp.Redirect: Unable to drop index `%s` from table `%s`: %s',
40 | 'redirect_timestamp_crawler',
41 | 'vdlp_redirect_clients',
42 | $e->getMessage()
43 | ));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/updates/20181117_0005_add_month_year_crawler_index_on_clients_table.php:
--------------------------------------------------------------------------------
1 | index(
19 | [
20 | 'month',
21 | 'year',
22 | 'crawler',
23 | ],
24 | 'month_year_crawler'
25 | );
26 |
27 | $table->index(
28 | [
29 | 'month',
30 | 'year',
31 | ],
32 | 'month_year'
33 | );
34 | });
35 | }
36 |
37 | public function down(): void
38 | {
39 | try {
40 | Schema::table('vdlp_redirect_clients', static function (Blueprint $table): void {
41 | $table->dropIndex('month_year_crawler');
42 | $table->dropIndex('month_year');
43 | });
44 | } catch (Throwable $e) {
45 | /** @var LoggerInterface $logger */
46 | $logger = resolve(LoggerInterface::class);
47 | $logger->error(sprintf(
48 | 'Vdlp.Redirect: Unable to drop index `%s`, `%s` from table `%s`: %s',
49 | 'month_year_crawler',
50 | 'month_year',
51 | 'vdlp_redirect_clients',
52 | $e->getMessage()
53 | ));
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/updates/20190404_0006_add_description_to_redirects_table.php:
--------------------------------------------------------------------------------
1 | string('description')
19 | ->nullable()
20 | ->after('system');
21 | });
22 | }
23 |
24 | public function down(): void
25 | {
26 | try {
27 | Schema::table('vdlp_redirect_redirects', static function (Blueprint $table): void {
28 | $table->dropColumn('description');
29 | });
30 | } catch (Throwable $e) {
31 | /** @var LoggerInterface $logger */
32 | $logger = resolve(LoggerInterface::class);
33 | $logger->error(sprintf(
34 | 'Vdlp.Redirect: Unable to drop index `%s` from table `%s`: %s',
35 | 'description',
36 | 'vdlp_redirect_redirects',
37 | $e->getMessage()
38 | ));
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/updates/20190704_0007_add_timestamp_crawler_index_on_clients_table.php:
--------------------------------------------------------------------------------
1 | index(
19 | [
20 | 'timestamp',
21 | 'crawler',
22 | ],
23 | 'timestamp_crawler'
24 | );
25 | });
26 | }
27 |
28 | public function down(): void
29 | {
30 | try {
31 | Schema::table('vdlp_redirect_clients', static function (Blueprint $table): void {
32 | $table->dropIndex('timestamp_crawler');
33 | });
34 | } catch (Throwable $e) {
35 | /** @var LoggerInterface $logger */
36 | $logger = resolve(LoggerInterface::class);
37 | $logger->error(sprintf(
38 | 'Vdlp.Redirect: Unable to drop index `%s` from table `%s`: %s',
39 | 'timestamp_crawler',
40 | 'vdlp_redirect_clients',
41 | $e->getMessage()
42 | ));
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/updates/20200408_0008_change_column_types_from_char_to_varchar.php:
--------------------------------------------------------------------------------
1 | getDriverName() === 'pgsql') {
18 | $database->statement(implode(' ', [
19 | 'ALTER TABLE vdlp_redirect_redirects',
20 | 'ALTER COLUMN match_type TYPE VARCHAR(12),',
21 | 'ALTER COLUMN target_type TYPE VARCHAR(12),',
22 | 'ALTER COLUMN from_scheme TYPE VARCHAR(5),',
23 | 'ALTER COLUMN to_scheme TYPE VARCHAR(5),',
24 | 'ALTER COLUMN status_code TYPE VARCHAR(3);',
25 | ]));
26 |
27 | $database->statement(implode(' ', [
28 | 'ALTER TABLE vdlp_redirect_redirects',
29 | "ALTER COLUMN target_type SET DEFAULT 'path_or_url',",
30 | "ALTER COLUMN from_scheme SET DEFAULT 'auto',",
31 | "ALTER COLUMN to_scheme SET DEFAULT 'auto';"
32 | ]));
33 |
34 | $database->statement(implode(' ', [
35 | 'ALTER TABLE vdlp_redirect_redirect_logs',
36 | 'ALTER COLUMN status_code TYPE VARCHAR(3);',
37 | ]));
38 | }
39 |
40 | if ($database->getDriverName() === 'mysql') {
41 | $database->statement('ALTER TABLE `vdlp_redirect_redirects` CHANGE `match_type` `match_type` VARCHAR(12) NULL DEFAULT NULL;');
42 | $database->statement("ALTER TABLE `vdlp_redirect_redirects` CHANGE `target_type` `target_type` VARCHAR(12) NOT NULL DEFAULT 'path_or_url';");
43 | $database->statement("ALTER TABLE `vdlp_redirect_redirects` CHANGE `from_scheme` `from_scheme` VARCHAR(5) NOT NULL DEFAULT 'auto';");
44 | $database->statement("ALTER TABLE `vdlp_redirect_redirects` CHANGE `to_scheme` `to_scheme` VARCHAR(5) NOT NULL DEFAULT 'auto';");
45 | $database->statement("ALTER TABLE `vdlp_redirect_redirects` CHANGE `status_code` `status_code` VARCHAR(3) NOT NULL DEFAULT '';");
46 | $database->statement("ALTER TABLE `vdlp_redirect_redirect_logs` CHANGE `status_code` `status_code` VARCHAR(3) NOT NULL DEFAULT '';");
47 | }
48 |
49 | // 'sqlite' does not support the char type, so it doesn't need to be altered.
50 | }
51 |
52 | public function down(): void
53 | {
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/updates/20200414_0009_add_ignore_case_to_redirects_table.php:
--------------------------------------------------------------------------------
1 | boolean('ignore_case')
15 | ->default(false)
16 | ->after('ignore_query_parameters');
17 | });
18 | }
19 |
20 | public function down(): void
21 | {
22 | try {
23 | Schema::table('vdlp_redirect_redirects', static function (Blueprint $table): void {
24 | $table->dropColumn('ignore_case');
25 | });
26 | } catch (Throwable $e) {
27 | /** @var LoggerInterface $logger */
28 | $logger = resolve(LoggerInterface::class);
29 | $logger->error(sprintf(
30 | 'Vdlp.Redirect: Unable to drop column `%s` from table `%s`: %s',
31 | 'ignore_case',
32 | 'vdlp_redirect_redirects',
33 | $e->getMessage()
34 | ));
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/updates/20200414_0010_add_ignore_trailing_slash_to_redirects_table.php:
--------------------------------------------------------------------------------
1 | boolean('ignore_trailing_slash')
15 | ->default(false)
16 | ->after('ignore_case');
17 | });
18 | }
19 |
20 | public function down(): void
21 | {
22 | try {
23 | Schema::table('vdlp_redirect_redirects', static function (Blueprint $table): void {
24 | $table->dropColumn('ignore_trailing_slash');
25 | });
26 | } catch (Throwable $e) {
27 | /** @var LoggerInterface $logger */
28 | $logger = resolve(LoggerInterface::class);
29 | $logger->error(sprintf(
30 | 'Vdlp.Redirect: Unable to drop column `%s` from table `%s`: %s',
31 | 'ignore_trailing_slash',
32 | 'vdlp_redirect_redirects',
33 | $e->getMessage()
34 | ));
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/updates/20200918_0011_refactor_redirects_logs_table.php:
--------------------------------------------------------------------------------
1 | getMessage(), PHP_EOL;
18 | echo 'Database table `vdlp_redirect_redirect_logs` could not be removed.', PHP_EOL;
19 | echo 'Please remove it manually and try running the database migrations again.', PHP_EOL;
20 |
21 | return;
22 | }
23 |
24 | Schema::create('vdlp_redirect_redirect_logs', static function (Blueprint $table): void {
25 | // Table MySQL configuration
26 | $table->engine = 'InnoDB';
27 |
28 | // Columns
29 | $table->increments('id');
30 | $table->unsignedInteger('redirect_id');
31 | $table->string('from_to_hash', 40);
32 | $table->string('status_code', 3);
33 | $table->mediumText('from_url');
34 | $table->mediumText('to_url');
35 | $table->unsignedInteger('hits')
36 | ->default(0);
37 | $table->timestamps();
38 |
39 | // Foreign keys
40 | $table->foreign('redirect_id', 'vdlp_redirect_log')
41 | ->references('id')
42 | ->on('vdlp_redirect_redirects')
43 | ->onDelete('cascade');
44 |
45 | // Indexes
46 | $table->unique([
47 | 'redirect_id',
48 | 'from_to_hash',
49 | 'status_code',
50 | ], 'redirect_log_unique');
51 | });
52 | }
53 |
54 | public function down(): void
55 | {
56 | try {
57 | Schema::disableForeignKeyConstraints();
58 | Schema::dropIfExists('vdlp_redirect_redirect_logs');
59 | Schema::enableForeignKeyConstraints();
60 | } catch (Throwable $throwable) {
61 | echo 'Migration error: ' . $throwable->getMessage(), PHP_EOL;
62 | echo 'Database table `vdlp_redirect_redirect_logs` could not be removed.', PHP_EOL;
63 | echo 'Please remove it manually and try running the database migrations again.', PHP_EOL;
64 |
65 | return;
66 | }
67 |
68 | Schema::create('vdlp_redirect_redirect_logs', static function (Blueprint $table): void {
69 | // Table MySQL configuration
70 | $table->engine = 'InnoDB';
71 |
72 | // Columns
73 | $table->increments('id');
74 | $table->unsignedInteger('redirect_id');
75 | $table->mediumText('from_url');
76 | $table->mediumText('to_url');
77 | $table->string('status_code', 3);
78 | $table->unsignedTinyInteger('day');
79 | $table->unsignedTinyInteger('month');
80 | $table->unsignedSmallInteger('year');
81 | $table->dateTime('date_time');
82 |
83 | // Indexes
84 | $table->index(['redirect_id', 'day', 'month', 'year'], 'redirect_log_dmy');
85 | $table->index(['redirect_id', 'month', 'year'], 'redirect_log_my');
86 |
87 | // Foreign keys
88 | $table->foreign('redirect_id', 'vdlp_redirect_log')
89 | ->references('id')
90 | ->on('vdlp_redirect_redirects')
91 | ->onDelete('cascade');
92 | });
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/updates/20200918_0012_add_redirect_id_to_system_request_logs_table.php:
--------------------------------------------------------------------------------
1 | unsignedInteger('vdlp_redirect_redirect_id')
18 | ->nullable()
19 | ->after('id');
20 |
21 | $table->foreign('vdlp_redirect_redirect_id', 'vdlp_redirect_request_log')
22 | ->references('id')
23 | ->on('vdlp_redirect_redirects')
24 | ->onDelete('set null');
25 | });
26 | }
27 |
28 | public function down(): void
29 | {
30 | if (Schema::hasColumn('system_request_logs', 'vdlp_redirect_redirect_id')) {
31 | Schema::table('system_request_logs', static function (Blueprint $table): void {
32 | $table->dropForeign('vdlp_redirect_request_log');
33 | $table->dropColumn('vdlp_redirect_redirect_id');
34 | });
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/updates/20231108_0013_add_keep_querystring_to_redirects_table.php:
--------------------------------------------------------------------------------
1 | boolean('keep_querystring')
18 | ->default(false)
19 | ->after('ignore_trailing_slash');
20 | });
21 | }
22 |
23 | public function down(): void
24 | {
25 | if (Schema::hasColumn('vdlp_redirect_redirects', 'keep_querystring')) {
26 | Schema::table('vdlp_redirect_redirects', static function (Blueprint $table): void {
27 | $table->dropColumn('keep_querystring');
28 | });
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/updates/version.yaml:
--------------------------------------------------------------------------------
1 | v1.0.0:
2 | - "Transfer vendor ownership. Adrenth.Redirect -> Vdlp.Redirect"
3 | - 20180718_0001_create_tables.php
4 | - 20180831_0002_upgrade_from_adrenth_redirect.php
5 | v1.1.0: "Redirect rules are now being cached (if caching enabled) -- See: https://github.com/vdlp/oc-redirect-plugin/issues/1"
6 | v1.2.0: "Add extra filters to the Redirects overview -- See: https://github.com/vdlp/oc-redirect-plugin/pull/5"
7 | v1.3.0:
8 | - "Add support for ignoring query parameters -- See: https://github.com/vdlp/oc-redirect-plugin/issues/6"
9 | - 20181019_0003_add_ignore_query_parameters_to_redirects_table.php
10 | v1.4.0: "Code dusting and improvements to the redirect list view"
11 | v1.4.1:
12 | - "Add extra statistics database index to improve performance"
13 | - 20181117_0004_add_redirect_timestamp_crawler_index_on_clients_table.php
14 | v1.4.2:
15 | - "Add extra statistics database index to improve performance (Statistics dashboard)"
16 | - 20181117_0005_add_month_year_crawler_index_on_clients_table.php
17 | v1.4.3: "Fix thrown BindingResolutionException on redirecting"
18 | v1.4.4: "Fixes a redirect loop bug which might occur after renaming content pages"
19 | v1.4.5: "Fixes critical issue with ignoring query parameters"
20 | v1.5.0: "Bugfixes and added more extensibility support -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.5.0"
21 | v1.6.0:
22 | - "Minor UI additions and added new Match Type: Regular Expressions! -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.6.0"
23 | - 20190404_0006_add_description_to_redirects_table.php
24 | v1.7.0: "Bugfixes and code improvements -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.7.0"
25 | v1.8.0: "Add CLI command for publishing redirects -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.8.0"
26 | v1.8.1: "Fix critical issue regarding regular expression redirects"
27 | v1.9.0: "Add setting for enabling/disabling automatic creation of redirects -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.9.0"
28 | v1.10.0:
29 | - "Improve statistics performance -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.10.0"
30 | - 20190704_0007_add_timestamp_crawler_index_on_clients_table.php
31 | v1.10.1: "This fixes an issue where redirects will fail to work when the redirects.csv file does not have the write permission -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.10.1"
32 | v1.10.2: "Fixes a fatal error when running TestLab -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.10.2"
33 | v1.10.3: "Fix support for Postgres -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.10.3"
34 | v1.10.4: "Fixes reported issues #41 and #43 -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.10.4"
35 | v1.10.5: "Fixes reported issues #46 and #49 -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/1.10.5"
36 | v2.0.0: "Supports PHP 7.1.3 and higher. Read CHANGELOG.md -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.0.0"
37 | v2.0.1: "Fix Middleware not being invoked in newer PHP versions -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.0.1"
38 | v2.0.2:
39 | - "Minor database and configuration fixes -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.0.2"
40 | - 20200408_0008_change_column_types_from_char_to_varchar.php
41 | v2.1.0: "Added support for October CMS L6 build, improved caching and more -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.1.0"
42 | v2.1.1: "Update CHANGELOG"
43 | v2.2.0: "Add cache control header and UI improvements -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.2.0"
44 | v2.3.0:
45 | - "Add new redirect options (ignore case and ignore trailing slash) -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.3.0"
46 | - 20200414_0009_add_ignore_case_to_redirects_table.php
47 | - 20200414_0010_add_ignore_trailing_slash_to_redirects_table.php
48 | v2.3.1: "Fix SQLSTATE[42S22] error when installing plugin -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.3.1"
49 | v2.3.2: "Improve error handling in plugin migration process -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.3.2"
50 | v2.4.0: "Skip requests with header 'X-Requested-With: XMLHttpRequest' -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.4.0"
51 | v2.4.1: "Add Redirect Extensions promo page -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.4.1"
52 | v2.5.0: "Add support for using relative paths -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.0"
53 | v2.5.1: "Fixes issues with redirect rules file not being present -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.1"
54 | v2.5.2: "Fix bug that causes re-writing the redirect rules file when hits are updated -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.2"
55 | v2.5.3: "Improve / fixes redirect rule caching -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.3"
56 | v2.5.4: "Add support for symfony/stopwatch:^5.0 (version 4.0 is still supported) -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.4"
57 | v2.5.5: "Suppress logging when redirect rules file is empty -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.5"
58 | v2.5.6: "Prevent connection exception when accessing settings in CLI mode -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.6"
59 | v2.5.7: "Improve redirect caching management -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.7"
60 | v2.5.8: "Improve redirect caching management (revised) -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.8"
61 | v2.5.9: "Fix import in Plugin file"
62 | v2.5.10: "Add PHP 8.0 version constraint and composer/installers package"
63 | v2.5.11: "Minor fixes -- See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/2.5.11"
64 | v2.5.12: "Fix strpos() type error"
65 | v2.5.13: "Fix database error when cache is being cleared before installation of plugin."
66 | v2.6.0: "Update plugin dependencies."
67 | v3.0.0:
68 | - "Drop support for October CMS 1.1 and lower. See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/3.0.0"
69 | - 20200918_0011_refactor_redirects_logs_table.php
70 | - 20200918_0012_add_redirect_id_to_system_request_logs_table.php
71 | v3.0.1: "Add support for regular expression matches in target path."
72 | v3.0.2: "Change version constraint for composer/installers."
73 | v3.0.3: "Update plugin dependencies"
74 | v3.0.4: "Improved compatibility/extensibility with other Plugins."
75 | v3.0.5: "Lock to October CMS version 2.x. Support for October CMS 3 will be added in v3.1.0."
76 | v3.1.0: "Add support for October CMS 3.x. Drop support for October CMS 2.x."
77 | v3.1.1: "Add description column to Redirects overview."
78 | v3.1.2: "Fix daily stats labels when selecting month/year."
79 | v3.1.3: "Maintenance release"
80 | v3.1.4: "Fix target field not loading properly. See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/3.1.4"
81 | v3.1.5: "Minor improvements. See: https://github.com/vdlp/oc-redirect-plugin/releases/tag/3.1.5"
82 | v3.1.6: "Add support for October CMS 3.3."
83 | v3.1.7: "Remove use of old BrandSetting constants."
84 | v3.1.8: "Add German translation."
85 | v3.1.9: "Added environment variables for manipulating the navigation."
86 | v3.1.10: "Fixed backend filtering for MySQL >= 8.0.3."
87 | v3.1.11:
88 | - "Add option to keep query string when redirecting."
89 | - 20231108_0013_add_keep_querystring_to_redirects_table.php
90 |
--------------------------------------------------------------------------------