Compare commits

...

170 Commits

Author SHA1 Message Date
slawkens
016138ab55 Release v0.8.17 2024-05-18 21:39:50 +02:00
slawkens
77efb80a12 Update config.php 2024-05-15 22:20:31 +02:00
slawkens
02eea950e4 Fix XSS in creatures.php, thanks to @gesior
Closes #254
2024-05-15 22:15:36 +02:00
slawkens
2793c41655 Set default status_ip, most server are hosted locally anyway 2024-05-15 22:07:46 +02:00
slawkens
62d3c198d5 Fix change_info if account_country is disabled 2024-04-15 21:55:02 +02:00
slawkens
ef62b53cec Don't allow redirect to external website 2024-04-08 19:05:42 +02:00
slawkens
7181b988e9 Add TwigTypeCastingExtension
Useful for casting variables in Twig
2024-04-08 07:35:48 +02:00
slawkens
8b0b123f42 deny vendor, composer.json, changelog.md etc. in nginx config sample 2024-04-06 19:51:57 +02:00
slawkens
f98332c698 Update .gitignore 2024-02-18 12:01:22 +01:00
slawkens
b1660bf27a Update README.md
[skip ci]
2024-02-17 09:06:54 +01:00
slawkens
191ad25eb2 Use word-break: break-all in guilds description + character comment 2024-02-16 20:39:40 +01:00
slawkens
7469be6efb Fix date 2024-02-12 21:48:50 +01:00
slawkens
47a3bfd265 Release v0.8.16 2024-02-12 21:48:17 +01:00
slawkens
5ae0be2323 Revert "Fix installation"
This reverts commit 9c318f9012.
2024-02-12 21:43:55 +01:00
slawkens
42154d55a0 Fix broken installation I introduced in 0.8.15 2024-02-12 21:39:04 +01:00
slawkens
9dcc08ee6e Seems that this is better solution to the #245 (output buffering)
This works for both, when output_buffering is enabled, and disabled
2024-01-30 19:20:18 +01:00
slawkens
ba537b42bb Remove 31.php migration -> was for develop branch 2024-01-30 18:23:20 +01:00
slawkens
9c318f9012 Fix installation 2024-01-28 11:11:51 +01:00
Danilo Pucci
a88103a956 - adding check before flush buffer (#245) 2024-01-01 23:31:26 +01:00
slawkens
e26e6f3a1c Silently ignore if the hook does not exist 2023-12-28 19:12:47 +01:00
Slawomir Boczek
08d67a07e0 Create SECURITY.md 2023-12-15 20:56:06 +01:00
slawkens
6e9a89cb2e Update common.php 2023-12-14 16:34:44 +01:00
slawkens
e3aa3d4031 Release v 0.8.15 2023-12-09 00:14:16 +01:00
slawkens
156a68f8bd Update phplint.yml 2023-12-09 00:11:01 +01:00
slawkens
6a28da5d33 Update phplint.yml 2023-12-09 00:04:20 +01:00
slawkens
ee32384dca Seems there was more XSS in bugtracker 2023-12-08 23:45:13 +01:00
slawkens
19afd73e8a This is better 2023-11-29 15:48:03 +01:00
slawkens
eead6a2975 Fix exception showing 2023-11-28 17:11:04 +01:00
slawkens
11b11dd3ee Release v0.8.14 2023-11-27 23:31:45 +01:00
slawkens
483155cf4c Prevent session fixation 2023-11-27 23:16:51 +01:00
slawkens
55dbade8d5 Fix XSS in forum 2023-11-27 22:58:24 +01:00
slawkens
d1bc63d07a Fix forum XSS 2023-11-27 22:58:00 +01:00
slawkens
83a91ec540 Fix XSS in bugtracker.php 2023-11-27 20:28:43 +01:00
slawkens
7b43c972dd Fix missing query_string in nginx sample config
Causes missing parameters in $_GET query
2023-11-25 16:34:57 +01:00
slawkens
3fdf1d3f44 require_once is better 2023-11-05 20:13:31 +01:00
slawkens
764db0c203 Fix display ban info on account page
https://otland.net/threads/myacc-bans-display-problem.286825/
2023-11-02 22:06:07 +01:00
slawkens
538076bc45 My fault 2023-09-26 22:00:45 +02:00
slawkens
4327b66f91 Clear some additional cache keys 2023-09-26 20:45:50 +02:00
slawkens
3f27724569 Update common.php 2023-09-16 10:46:17 +02:00
slawkens
9c0c2bbece Update CHANGELOG.md 2023-09-16 10:45:54 +02:00
slawkens
946144016b Release v0.8.13 2023-09-16 10:35:10 +02:00
slawkens
5c3b01aca4 Fix XSS vulnerability 2023-09-16 10:31:33 +02:00
slawkens
50983a2b85 Fix error log when coins column does not exist 2023-09-14 16:29:31 +02:00
slawkens
765886f0c7 Add latest clients versions 2023-08-31 14:20:49 +02:00
slawkens
8ea78a5852 thanks @elsongabriel, seems str_contains is not available in php 7 2023-08-25 20:45:45 +02:00
slawkens
063cbab93e Allow hooks to be prefixed with HOOK_ 2023-08-23 12:00:03 +02:00
slawkens
f1670f4012 Patching from develop - twig context for hooks 2023-08-21 12:25:53 +02:00
slawkens
6fcf0f7117 Ignore gallery 2023-08-21 12:21:24 +02:00
slawkens
7a07763625 Update README.md 2023-08-11 22:21:54 +02:00
slawkens
8d2172a649 Added JetBrains logo + notice, thanks for support! 2023-08-11 22:17:17 +02:00
slawkens
b8f65207b6 Add version support table + fix badges 2023-08-11 22:11:29 +02:00
slawkens
ea675afe86 Start 0.8.13-dev 2023-08-07 22:53:02 +02:00
slawkens
cc1cebf359 Update CHANGELOG & release v0.8.12 2023-08-07 22:14:47 +02:00
slawkens
1e874c7027 Fixed not working links from database, introduced in 0.8.10 2023-08-07 21:45:56 +02:00
slawkens
a338fd967c Removed deprecated functions: utf8_encode & decode 2023-08-05 19:58:52 +02:00
slawkens
8796ff7e72 Remove whitespaces 2023-08-05 19:58:20 +02:00
slawkens
a8172a518f Add some functions to compatibility layer of gesioraac 2023-08-05 19:58:04 +02:00
slawkens
559c2c7bd2 Add .htaccess to .gitignore 2023-08-05 11:57:15 +02:00
slawkens
7a546e5a41 There is no more info. That never worked. 2023-07-29 07:26:03 +02:00
slawkens
5f7a9154b7 Thanks @anyeor for previous fix 2023-07-11 11:17:18 +02:00
slawkens
0d52978d9f Fix: cannot create topic on this board (check wasn't working) 2023-07-11 11:15:58 +02:00
slawkens
df48363ea4 Shorten some forum code about length 2023-07-07 17:15:13 +02:00
slawkens
34725e0257 Forum: better error messages (Suggested by @anyeor) 2023-07-07 14:34:26 +02:00
slawkens
df321154f6 Fix guild description on guilds page 2023-07-02 13:47:32 +02:00
slawkens
f2a3ec1185 Fix guild description not shown 2023-06-30 19:53:16 +02:00
slawkens
ce4aed0f17 Add word-break on forum thread & reply
When someone inserts long word, is will break into multiple lines
2023-06-30 19:32:47 +02:00
slawkens
d0c82f6fb0 Start 0.8.12-dev 2023-06-30 19:13:38 +02:00
slawkens
89b76e721d Release 0.8.11 2023-06-30 17:12:38 +02:00
slawkens
6091290efe Update CHANGELOG.md 2023-06-30 17:12:02 +02:00
slawkens
e4c4990e7f Forum: Fix quote and edit post buttons not being shown 2023-06-30 15:46:25 +02:00
slawkens
4f1235bfe9 Fix twig exception thrown when player does not exist 2023-06-28 15:15:14 +02:00
slawkens
bf9d440a95 Fix BASE_DIR when accessing /tools 2023-06-27 18:17:37 +02:00
slawkens
59a149c253 Move <base href> above, so it works, thanks @Leesneaks 2023-06-27 18:15:54 +02:00
slawkens
563099f290 Revert "<base> is not working properly, use full URL instead"
This reverts commit fa015b8d39.
2023-06-27 17:44:19 +02:00
slawkens
3732bf988d More changes to deleted characters (Account, guilds)
Account: Cannot change name, comment, gender
+ Cannot be deleted if owns a guild
Guilds: Cannot create, cannot be invited, cannot accept invite, cannot be passed leadership to
2023-06-27 17:41:04 +02:00
slawkens
ab964fa1de Important fix: Not allow create char if limit is exceeded (by @anyeor )
Could have been used to spam database, now it doesn't ignore deleted characters

He is not my brother :P Just same last name
2023-06-27 15:02:28 +02:00
slawkens
b5c694224e code formatting 2023-06-27 14:50:44 +02:00
slawkens
23810345f6 small adjustments 2023-06-25 08:38:45 +02:00
slawkens
b574a29331 Better Gesior support 2023-06-22 22:15:18 +02:00
slawkens
6593e32d83 Change title to "Support in game" 2023-06-19 08:04:46 +02:00
slawkens
b09adc836d Nothing important, just some comments and small code style fixes 2023-06-19 08:03:47 +02:00
slawkens
dcf9a45974 Do not display warning if HTTP_ACCEPT_LANGUAGE is not set 2023-06-19 08:01:35 +02:00
slawkens
21258313ef New function Cache::remember($key, $ttl, $callback) 2023-06-19 08:01:11 +02:00
slawkens
f851fa3845 New characters page hooks
HOOK_CHARACTERS_BEFORE_SKILLS
HOOK_CHARACTERS_AFTER_SKILLS
HOOK_CHARACTERS_AFTER_QUESTS
HOOK_CHARACTERS_AFTER_EQUIPMENT
HOOK_CHARACTERS_BEFORE_DEATHS
2023-06-19 08:00:54 +02:00
slawkens
2fdd507902 Display warning if hook file does not exist 2023-06-19 08:00:11 +02:00
slawkens
b850e56ff1 Use $i for hooks, easier compare 2023-06-19 07:47:49 +02:00
SRNT-GG
8d10082179 WIP - Removing unneccessary closing tags to prevent potential issues. (#223)
* Part 1

Removing closing tags when no HTML or other output comes after the last PHP codeblock.

* Further removals

* nothing

---------

Co-authored-by: slawkens <slawkens@gmail.com>
2023-06-15 20:53:55 +02:00
SRNT-GG
996ae625c9 Update README.md (#224)
updated required php version
2023-06-11 18:41:47 +02:00
slawkens
467f7ef927 Rename to .htaccess.dist
Causes problems on default setup
2023-06-03 09:04:41 +02:00
slawkens
fa015b8d39 <base> is not working properly, use full URL instead 2023-06-02 15:26:09 +02:00
slawkens
4b4864561c Better news back button 2023-06-01 11:23:28 +02:00
slawkens
475cea8549 Change button style (characters - view)
was causing issues in other templates
2023-06-01 09:57:20 +02:00
slawkens
760214fdbd Init $account_logged, if no logged 2023-06-01 08:52:22 +02:00
slawkens
9c5dcd7b19 Add cypress/e2e/2-advanced-examples to .gitignore 2023-05-29 08:20:56 +02:00
slawkens
720e400f7c Add cypress.env.json to .gitignore 2023-05-29 08:20:33 +02:00
slawkens
c261c6ba48 Add line & file to exception handler 2023-05-29 08:19:58 +02:00
slawkens
933d4e1d6f Release 0.8.10 2023-05-18 19:29:53 +02:00
slawkens
1d08833726 Update CHANGELOG.md 2023-05-18 19:25:50 +02:00
slawkens
7cfca55e3c PHP 7.2.5 is now required, cause of Twig 2.x 2023-05-18 19:21:13 +02:00
slawkens
7e13b62b8f Fix Twig error on create account 2023-05-18 19:06:50 +02:00
slawkens
5ccfcd541e Allow pages to be placed in templates folder (second attempt) 2023-05-14 08:59:34 +02:00
slawkens
ba4d2a9c48 Dirty workaround for fb links 2023-05-14 08:38:20 +02:00
slawkens
7a61f613ec Revert "Workaround for links from fb, like ?fbclid=x"
This reverts commit 073d9da0bc.
2023-05-14 08:15:44 +02:00
slawkens
073d9da0bc Workaround for links from fb, like ?fbclid=x
Now shows news page, instead of "not found".
For 0.9 there is better solution
2023-05-13 10:20:58 +02:00
slawkens
e081a67589 Print more info if character cannot be created 2023-04-12 12:52:05 +02:00
slawkens
37a27b8065 Add check for player_deaths columns 2023-04-01 15:09:10 +02:00
slawkens
d34f7eb2fc Exclude polyfill-mbstring/bootstrap80.php 2023-03-31 11:18:58 +02:00
slawkens
f6c080cb5c Add overtrue/phplint@7.4 2023-03-31 10:26:16 +02:00
slawkens
a983fd03b1 Revert "test github actions"
This reverts commit 5b651886a5.
2023-03-31 09:34:21 +02:00
slawkens
5b651886a5 test github actions 2023-03-31 09:30:28 +02:00
slawkens
6484ab75d9 Do not allow to continue install when there is no server database imported 2023-03-26 00:17:55 +01:00
slawkens
becad18465 fix small bug on install - please fill all input 2023-03-26 00:02:25 +01:00
slawkens
ec7e5a8838 Fix when server uses another items serializer 2023-03-25 21:59:13 +01:00
slawkens
300c1b4ebc Fix cannot go forward when config.local.php cannot be saved 2023-03-19 14:46:57 +01:00
slawkens
4f0dd89eb9 Change from warning to error (config.local.php save error) 2023-03-19 14:46:15 +01:00
slawkens
79f7c3dbd4 nothing important 2023-03-19 14:34:29 +01:00
slawkens
f24fc75b12 Bump version to 0.8.10-dev 2023-03-16 10:41:57 +01:00
slawkens
4fcc71e127 Update CHANGELOG.md 2023-03-16 09:55:05 +01:00
slawkens
403b4aa89b Release v0.8.9 2023-03-16 09:44:26 +01:00
slawkens
613bcf379b Update CHANGELOG.md 2023-03-16 09:44:06 +01:00
slawkens
8f2cc2ca38 fix rel path 2023-03-16 09:03:45 +01:00
slawkens
cdae11226d add PLUGINS dir to twig paths 2023-03-15 18:05:24 +01:00
slawkens
79fd97ad78 plugins folder should be accessible from public 2023-03-15 18:03:24 +01:00
slawkens
b477d4c821 fix installer hang on 2023-03-07 09:28:52 +01:00
slawkens
289f82ad23 Update nginx-sample.conf 2023-03-06 08:27:26 +01:00
slawkens
92569b7965 patch some changes
add contributors
2023-03-01 10:36:38 +01:00
slawkens
c03b041f40 add .git to denied folders in nginx 2023-02-28 19:05:34 +01:00
slawkens
2ac8ed7411 more php 8.x compatibility 2023-02-18 21:23:21 +01:00
slawkens
3280b3b9df Update tables.headline.html.twig 2023-02-18 21:10:02 +01:00
slawkens
05c37b94bb Create account.back_button.html.twig 2023-02-18 21:09:56 +01:00
slawkens
a91e7226dc new buttons code for tibiacom template, can create button with any text 2023-02-18 21:09:51 +01:00
slawkens
a39600efe2 fix player save on tfs 1.5 with new ipv6 2023-02-18 20:57:55 +01:00
slawkens
4fd5922784 You can now disable status checking for testing purposes
Useful for local testing when there is no server running
2023-02-18 11:44:32 +01:00
slawkens
b3d1274ffe Release v0.8.8 2023-02-18 11:14:45 +01:00
slawkens
9de49b4b6a Update CHANGELOG.md 2023-02-18 11:13:56 +01:00
slawkens
e6a368c3ac Update CHANGELOG.md 2023-02-18 11:12:51 +01:00
slawkens
3dca1b519a 760 is correct permission 2023-02-16 10:16:07 +01:00
slawkens
ae8af396f4 fix #136 2023-02-16 08:56:08 +01:00
slawkens
38294420d5 patch from develop, IS_CLI fixes 2023-02-07 22:49:01 +01:00
slawkens
c0dee61add accounts.block has been removed 2023-02-07 22:46:47 +01:00
slawkens
a84c92e007 allow template pages to be placed in templates dir 2023-02-07 22:46:35 +01:00
slawkens
60a854e5fd new function> escapeHtml + fix css in admin menus 2023-02-06 17:39:23 +01:00
slawkens
fa9f7aab7c accounts.blocked is not used by AAC 2023-02-03 17:26:50 +01:00
slawkens
d697a556c2 Update online.php 2023-02-03 17:21:54 +01:00
slawkens
802fd831cb (probably) fix #204 2023-02-03 17:21:30 +01:00
slawkens
52ca8a844a Fix #178 2023-02-03 16:13:53 +01:00
slawkens
573fc819d3 fix db table detection failure 2023-02-03 16:05:21 +01:00
slawkens
ead9d79cb1 fix #185 2023-02-03 15:36:57 +01:00
slawkens
43c197316a feature: mail confirmed reward
Suggested by @EPuncker
2023-02-03 14:39:09 +01:00
slawkens
c318d3a9de Option to disable plugin adjusted 2023-02-03 14:09:39 +01:00
slawkens
80d3f5ffe8 Fix logout hook & add images/editor to .gitignore 2023-02-02 20:54:47 +01:00
slawkens
f9d85b10b7 Update .gitignore 2023-02-02 16:24:12 +01:00
slawkens
4028a58adc Update OTS_DB_PDOQuery_PHP71.php 2023-02-02 16:20:09 +01:00
slawkens
0a3a079b86 PHP 8.1 compatibility 2023-02-02 16:19:28 +01:00
slawkens
d691148c84 Revert "Fix compatibility with PHP 8.1"
This reverts commit 99338afacb.
2023-02-02 16:17:33 +01:00
slawkens
48f74b9c7a Update tinymce to v4.9.11 (latest release in 4.x series)
Taken from composer
2023-02-02 11:51:44 +01:00
slawkens
99338afacb Fix compatibility with PHP 8.1 2023-02-02 11:15:17 +01:00
slawkens
301c3b86e2 Add fill-mbstring, which is required by twig 2023-02-02 10:42:47 +01:00
slawkens
130f7ba405 Update Twig to v2.15.4 2023-02-02 10:37:45 +01:00
slawkens
e552bcfe82 Fix ipv6 introduced in latest TFS 2022-12-16 23:05:43 +01:00
the-overdriven
ad75499a91 Update admin.news.form.html.twig (#207)
rename Ticket to Ticker
2022-11-28 08:17:58 +01:00
slawkens
7ddcb441c8 nothing important..
some visual fixes
2022-11-04 09:28:51 +01:00
slawkens
99da8dbec1 Update account.change_mail.html.twig 2022-10-28 13:41:39 +02:00
slawkens
743d5164b3 Add more client versions 2022-10-28 13:41:35 +02:00
slawkens
1f7dfdca50 Add vocation into getTopPlayers 2022-10-28 13:41:23 +02:00
slawkens
2164d59331 Fix typo in br locale 2022-10-28 13:41:10 +02:00
slawkens
0d845b764b Add exception class
from develop
2022-10-28 13:40:16 +02:00
slawkens
0a2cd69a4b Add compat Gesior classes
To allow more custom pages be used with myaac
2022-09-12 14:16:36 +02:00
slawkens
ddb60fa1e0 Bump version to 0.8.8-dev 2022-09-12 11:13:21 +02:00
slawkens
b7e33c5e6d Fix config.account_premium_days for TFS 1.4+ 2022-09-10 21:37:42 +02:00
575 changed files with 9279 additions and 4884 deletions

16
.github/workflows/phplint.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: PHP Linting
on:
pull_request:
branches: [master]
push:
branches: [master]
jobs:
phplint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: overtrue/phplint@3.4.0
with:
path: .
options: --exclude="system/libs/polyfill-mbstring/bootstrap80.php"

23
.gitignore vendored
View File

@@ -2,12 +2,21 @@ Thumbs.db
.DS_Store
.idea
#
/.htaccess
# composer
composer.lock
vendor
# npm
node_modules
tools/ext
# cypress
cypress.env.json
cypress/e2e/2-advanced-examples
cypress/screenshots
# created by release.sh
releases
@@ -24,6 +33,16 @@ templates/*
images/guilds/*
!images/guilds/default.gif
# editor images
images/editor/*
!images/editor/index.html
# gallery images
images/gallery/*
!images/gallery/index.html
!images/gallery/demon.jpg
!images/gallery/demon_thumb.gif
# cache
system/cache/*
!system/cache/index.html
@@ -33,7 +52,7 @@ system/cache/*
# php sessions
system/php_sessions/*
!system/php_sessions//index.html
!system/php_sessions/index.html
# logs
system/logs/*
@@ -49,6 +68,8 @@ plugins/*
!plugins/example.json
!plugins/account-create-hint.json
!plugins/account-create-hint
!plugins/email-confirmed-reward.json
!plugins/email-confirmed-reward
landing
# others/rest

View File

@@ -1,5 +1,152 @@
# Changelog
## [0.8.17 - 15.04.2024]
### Added
* TwigTypeCastingExtension (https://github.com/slawkens/myaac/commit/7181b988e9518320d57486670ca4e2d3b2fe1cfa)
### Fixed
* fix XSS in creatures.php (https://github.com/slawkens/myaac/commit/02eea950e4fd756e8d5c32e56181986d51f5ac70, @gesior)
* don't allow redirect to external website (https://github.com/slawkens/myaac/commit/ef62b53cec5a479cc85aa15940ad9ebbcefde876)
* change_info if account_country is disabled (https://github.com/slawkens/myaac/commit/62d3c198d567541a90900fe2d7ede070e7b1ff68)
### Changed
* use word-break: break-all in guilds description + character comment (https://github.com/slawkens/myaac/commit/191ad25eb2d4c1cec6f6668da7a345fec0ad2a7f)
* set default status_ip to 127.0.0.1, most server are hosted locally anyway (https://github.com/slawkens/myaac/commit/2793c41655b47f7db295143a298ccda70f11462b)
## [0.8.16 - 12.02.2024]
### Fixed
* broken installation
* database and finish step warnings/errors (https://github.com/slawkens/myaac/pull/245, @danilopucci)
* silently ignore if the hook does not exist
## [0.8.15 - 09.12.2023]
More security fixes, especially in bugtracker.
## [0-8.14 - 27.11.2023]
Security fixes.
### Fixed
* XSS vulnerability in bugtracker (https://github.com/slawkens/myaac/commit/83a91ec540072d319dd338abff45f8d5ebf48190)
* XSS vulnerability in forum (https://github.com/slawkens/myaac/commit/d1bc63d07ad88a143358cacd2c417891eea74dcc + https://github.com/slawkens/myaac/commit/55dbade8d5280c5baed45e5f7ebc3613b8e9b9e8)
* Session Fixation (https://github.com/slawkens/myaac/commit/483155cf4c1e3068aaee0d44541dfa61f6223379)
* displaying ban info on account page (https://github.com/slawkens/myaac/commit/764db0c203d1826ffce3a5a78f83a97e56bd0685)
### Changed
* Clear some additional cache keys - like database cache (https://github.com/slawkens/myaac/commit/4327b66f915d06dce504211692173606b9ef3b4e)
## [0.8.13 - 16.09.2023]
### Added
* latest client versions to config (https://github.com/slawkens/myaac/commit/765886f0c782807400c429577cde5e45bd7c308f)
* patching from develop - twig context for hooks (https://github.com/slawkens/myaac/commit/f1670f4012cc7595433fe0b1937c1f9b15a60b07)
### Fixed
* fixed XSS vulnerability in some pages (https://github.com/slawkens/myaac/commit/5c3b01aca4f3cfe8abc86b8ce48194b2da87b808)
Nothing more or less!
## [0.8.12 - 07.08.2023]
I've moved the repository back to my personal account. (Just so you know!)
I will also try to add git commits pointed to each change, lets see if you like it or not - you can comment in discussion, that will be created just after releasing this version :)
### Added
* forum: better error messages (Suggested by @anyeor) (https://github.com/slawkens/myaac/commit/34725e0257684fe5fa43875cc3a8f587ba04642e)
* more support for GesiorAAC classes, so some of them will work with MyAAC (https://github.com/slawkens/myaac/commit/a8172a518ff8939c4402349b16c064fcaf855d31)
* word-break on forum thread & reply (Suggested by @anyeor) (https://github.com/slawkens/myaac/commit/ce4aed0f1719d2aadc749e5238e883e3c10e2686)
### Fixed
* not working pages/links from database, introduced in 0.8.10 (Thanks to OtLand user - https://otland.net/members/0lo.99657/ for report) (https://github.com/slawkens/myaac/commit/1e874c7027769bd09e772a1cdac75d7e37991256)
* it was possible to create topic in board that was closed, ommiting the error check (Thanks to @anyeor for report) (https://github.com/slawkens/myaac/commit/0d52978d9fb99869500d35e7676f454ca5eaba14)
* PHP 8.2 compatibility - removed deprecated functions utf8_encode & utf8_decode (https://github.com/slawkens/myaac/commit/a338fd967cdbcc89e86be4e6b66b2cad2ff23251)
* guild description not being correctly shown (Reported by @anyeor) (https://github.com/slawkens/myaac/commit/f2a3ec1185df64ad9084d4ff55790ae4a5b3e5fd, https://github.com/slawkens/myaac/commit/df321154f63d458a4bc7d83bac5e3447b67317a4)
### Removed
* Some old code for verifying messages length (Reported by @anyeor) (https://github.com/slawkens/myaac/commit/df48363ea4ced4350fd90ffddf57d464ba5afa8b)
* some info about config failed to load, was never working (https://github.com/slawkens/myaac/commit/7a546e5a41036b0e9e926d337c6f2e3c41c591d2)
## [0.8.11 - 30.06.2023]
### Added
* new function from 0.9 - Cache::remember($key, $ttl, $callback)
* new characters page hooks
* line number & file to exception handler, to easier localize exceptions
### Changed
* rename to .htaccess.dist, causes some problems on default setup
* removing unneccessary PHP closing tags to prevent potential issues (by @SRNT-GG)
* display warning if hook file does not exist
### Fixed
* important: Not allow create char if limit is exceeded (by @anyeor) could have been used to spam database
* deleted chars: cannot change comment, name, gender, cannot create guild, cannot be invited, cannot accept invite, cannot be passed leadership to
* forum: quote and edit post buttons not being shown
* twig exception thrown when player does not exist, on character change comment (thanks @anyeor)
* BASE_DIR when accessing /tools
* do not display warning if HTTP_ACCEPT_LANGUAGE is not set
## [0.8.10 - 18.05.2023]
### Changed
* PHP 7.2.5 is now required, cause of Twig 2.x
* allow pages to be placed in templates folder, under pages/ subfolder
### Fixed
* Twig error with global variable on create account
* links/redirects from facebook, etc. like ?fbclid=x
* do not allow to continue install when there is no server database imported
* cannot go forward when config.local.php cannot be saved
* when server uses another items serializer
* small bug on install - please fill all input
## [0.8.9 - 16.03.2023]
### Added
* You can now disable server status checking for testing purposes, useful for local testing when there is no server running
* with this, the page won't need 2 seconds to load
* set status_enabled to false in config.php
* new buttons code for tibiacom template, can create button with any text
* patched some small changes from develop branch
### Changed
* add .git to denied folders in nginx-sample.conf
* plugins folder is now accessible from outside
* add plugins folder to twig search paths
### Fixed
* player save on tfs 1.5 with new ipv6
* more php 8.x compatibility
* rel path for exception message, causing message to be not in red background
## [0.8.8 - 18.02.2023]
### Added
* mail confirmed reward
* support for latest group changes in TFS
* new function: escapeHtml
### Updated
* TinyMCE to v4.9.1 (latest release in 4.x series)
* Twig to v2.15.4
### Changed
* you can now place custom pages in your template directory under pages/ folder
* HOOK_LOGOUT parameters, now only account_id is passed
### Fixed
* ipv6 introduced in latest TFS
* config.account_premium_days for TFS 1.4+
* better compatibility with GesiorAAC
* PHP 8.1 compatibility
* myaac_ db table detection failure
* reload creatures error, when items cache has been cleared
### Removed
* accounts.blocked column, which is not used by AAC
## [0.8.7 - 31.08.2022]
### Added

14
CONTRIBUTORS.txt Normal file
View File

@@ -0,0 +1,14 @@
# automatically exported using this script:
# git log --all --format='%cN <%cE>' | sort -u > contributors
# in no particular order
# cleaned for readability
Evil Puncker <EPuncker@users.noreply.github.com>
Fernando Matos <fernando@pixele.com.br>
Lee <42119604+Leesneaks@users.noreply.github.com>
caio <caio.zucoli@gmail.com>
slawkens <slawkens@gmail.com>
tobi132 <52947952+tobi132@users.noreply.github.com>
vankk <nwtr.otland@hotmail.com>
whiteblXK <krzys16001@gmail.com>
xitobuh <jonas.hockert92@gmail.com>

View File

@@ -1,2 +1,3 @@
* Gesior.pl (2007 - 2008)
* Slawkens (2009 - 2020)
* Slawkens (2009 - 2023)
* Contributors listed in CONTRIBUTORS.txt

View File

@@ -1,22 +1,29 @@
# [MyAAC](https://my-aac.org)
[![Build Status Master](https://img.shields.io/travis/slawkens/myaac/master)](https://travis-ci.org/github/slawkens/myaac)
[![License: GPL-3.0](https://img.shields.io/github/license/slawkens/myaac)](https://opensource.org/licenses/gpl-license)
[![Downloads Count](https://img.shields.io/github/downloads/slawkens/myaac/total)](https://github.com/slawkens/myaac/releases)
[![PHP Versions](https://img.shields.io/travis/php-v/slawkens/myaac/master)](https://github.com/slawkens/myaac/blob/d8b3b4135827ee17e3c6d41f08a925e718c587ed/.travis.yml#L3)
[![OpenTibia Discord](https://img.shields.io/discord/288399552581468162)](https://discord.gg/2J39Wus)
[![Closed Issues](https://img.shields.io/github/issues-closed-raw/slawkens/myaac)](https://github.com/slawkens/myaac/issues?q=is%3Aissue+is%3Aclosed)
MyAAC is a free and open-source Automatic Account Creator (AAC) written in PHP. It is a fork of the [Gesior](https://github.com/gesior/Gesior2012) project. It supports only MySQL databases.
Official website: https://my-aac.org
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/slawkens/myaac/cypress.yml)](https://github.com/slawkens/myaac/actions)
[![License: GPL-3.0](https://img.shields.io/github/license/slawkens/myaac)](https://opensource.org/licenses/gpl-license)
[![Downloads Count](https://img.shields.io/github/downloads/slawkens/myaac/total)](https://github.com/slawkens/myaac/releases)
[![OpenTibia Discord](https://img.shields.io/discord/288399552581468162)](https://discord.gg/2J39Wus)
[![Closed Issues](https://img.shields.io/github/issues-closed-raw/slawkens/myaac)](https://github.com/slawkens/myaac/issues?q=is%3Aissue+is%3Aclosed)
| Version | Status | Branch | Requirements |
|:--------|:-----------------------|:--------|:---------------|
| **1.x** | **Active development** | develop | **PHP >= 8.1** |
| 0.9.x | Not developed anymore | 0.9 | PHP >= 7.2.5 |
| 0.8.x | Active support | master | PHP >= 7.2.5 |
| 0.7.x | End Of Life | 0.7 | PHP >= 5.3.3 |
### Requirements
- PHP 5.6 or later
- MySQL database
- PDO PHP Extension
- XML PHP Extension
- ZIP PHP Extension
- (optional) mod_rewrite to use friendly_urls
- PHP Extensions: pdo, xml, json
- (optional) apache2 mod_rewrite (to use friendly_urls)
- (optional) zip PHP Extension (to install plugins)
- (optional) gd PHP Extension (for generating signature images)
### Installation
@@ -34,13 +41,14 @@ MyAAC is a free and open-source Automatic Account Creator (AAC) written in PHP.
chmod 660 images/guilds
chmod 660 images/houses
chmod 660 images/gallery
chmod -R 770 system/cache
chmod -R 760 system/cache
Visit http://your_domain/install (http://localhost/install) and follow instructions in the browser.
### Configuration
Check *config.php* to get more informations.
Check *config.php* to get more informations. (Notice: MyAAC 1.0+ doesn't use config.php anymore, it has been moved to Admin Panel - Settings page).
Use *config.local.php* for your local configuration changes.
### Branches
@@ -57,12 +65,13 @@ That means, we use:
- Some compatibility issues with some exotical distibutions.
### Contributing
Contributions are more than welcome.
Pull requests should be made to the Dev branch as that is the working branch, master is for release code.
Pull requests should be made to the *develop* branch as that is the working branch, master is for release code.
Bug fixes to current release should be done to master branch.
Look: [Contributing](https://github.com/otsoft/myaac/wiki/Contributing) in our wiki.
@@ -70,6 +79,12 @@ Look: [Contributing](https://github.com/otsoft/myaac/wiki/Contributing) in our w
If you have a great idea or want contribute to the project - visit our website at https://www.my-aac.org
## Project supported by JetBrains
Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects.
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/slawkens)
### License
This program and all associated files are released under the GNU Public License.

16
SECURITY.md Normal file
View File

@@ -0,0 +1,16 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.x.y | :white_check_mark: |
| 0.9.x | :x: |
| 0.8.x | :white_check_mark: |
| < 0.7 | :x: |
## Reporting a Vulnerability
If you found a security vulnerability, please write an email to security@my-aac.org
All reports will be taken very seriously, and a fix will be posted as soon as possible.

View File

@@ -1 +1,2 @@
<?php
<?php
// nothing yet here

View File

@@ -2,6 +2,9 @@
// few things we'll need
require '../common.php';
define('ADMIN_PANEL', true);
define('MYAAC_ADMIN', true);
if(file_exists(BASE . 'config.local.php')) {
require_once BASE . 'config.local.php';
}
@@ -12,8 +15,6 @@ if(file_exists(BASE . 'install') && (!isset($config['installed']) || !$config['i
throw new RuntimeException('Setup detected that <b>install/</b> directory exists. Please visit <a href="' . BASE_URL . 'install">this</a> url to start MyAAC Installation.<br/>Delete <b>install/</b> directory if you already installed MyAAC.<br/>Remember to REFRESH this page when you\'re done!');
}
define('ADMIN_PANEL', true);
$content = '';
// validate page
@@ -67,4 +68,4 @@ ob_end_clean();
// template
$template_path = 'template/';
require ADMIN . $template_path . 'template.php';
?>

View File

@@ -279,7 +279,13 @@ else if ($id > 0 && isset($account) && $account->isLoaded()) {
<?php
$acc_group = $account->getAccGroupId();
if ($hasTypeColumn) {
$acc_type = array("Normal", "Tutor", "Senior Tutor", "Gamemaster", "God"); ?>
$groups = new OTS_Groups_List();
$acc_type = array("Normal", "Tutor", "Senior Tutor", "Gamemaster", "God");
if ($groups->getHighestId() == 6) {
$acc_type = array("Normal", "Tutor", "Senior Tutor", "Gamemaster", "Community Manager", "God");
}
?>
<div class="col-xs-6">
<label for="group" class="control-label">Account Type:</label>
<select name="group" id="group" class="form-control">
@@ -420,7 +426,7 @@ else if ($id > 0 && isset($account) && $account->isLoaded()) {
<div class="box-body">
<form action="<?php echo $base; ?>" method="post">
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="search_name" value="<?php echo $search_account; ?>"
<input type="text" class="form-control" name="search_name" value="<?php echo escapeHtml($search_account); ?>"
maxlength="32" size="32">
<span class="input-group-btn">
<button type="submit" type="button" class="btn btn-info btn-flat">Search</button>

View File

@@ -10,8 +10,8 @@
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Load items.xml';
require LIBS . 'items.php';
require LIBS . 'weapons.php';
require_once LIBS . 'items.php';
require_once LIBS . 'weapons.php';
$twig->display('admin.items.html.twig');

View File

@@ -89,7 +89,7 @@ if (isset($_REQUEST['template'])) {
if (isset($menus[$id])) {
$i = 0;
foreach ($menus[$id] as $menu) {
echo '<li class="ui-state-default" id="list-' . $id . '-' . $i . '"><label>Name:</label><input type="text" name="menu[' . $id . '][]" value="' . $menu['name'] . '"/>
echo '<li class="ui-state-default" id="list-' . $id . '-' . $i . '"><label>Name:</label><input type="text" name="menu[' . $id . '][]" value="' . escapeHtml($menu['name']) . '"/>
<label>Link:</label><input type="text" name="menu_link[' . $id . '][]" value="' . $menu['link'] . '"/>
<input type="hidden" name="menu_blank[' . $id . '][]" value="0" />
<label><input class="blank-checkbox" type="checkbox" ' . ($menu['blank'] == 1 ? 'checked' : '') . '/><span title="Open in New Window">Open in New Window</span></label>

View File

@@ -117,7 +117,7 @@ if($action == 'edit' || $action == 'new') {
'news_link_form' => '?p=news&action=' . ($action == 'edit' ? 'edit' : 'add'),
'news_id' => isset($id) ? $id : null,
'title' => isset($p_title) ? $p_title : '',
'body' => isset($body) ? htmlentities($body, ENT_COMPAT, 'UTF-8') : '',
'body' => isset($body) ? escapeHtml($body) : '',
'type' => isset($type) ? $type : null,
'player' => isset($player) && $player->isLoaded() ? $player : null,
'player_id' => isset($player_id) ? $player_id : null,

View File

@@ -105,7 +105,7 @@ $twig->display('admin.pages.form.html.twig', array(
'title' => $p_title,
'php' => $php,
'enable_tinymce' => $enable_tinymce,
'body' => isset($body) ? htmlentities($body, ENT_COMPAT, 'UTF-8') : '',
'body' => isset($body) ? escapeHtml($body) : '',
'groups' => $groups->getGroups(),
'access' => $access
));
@@ -196,5 +196,3 @@ class Pages
return !count($errors);
}
}
?>

View File

@@ -697,7 +697,14 @@ else if ($id > 0 && isset($player) && $player->isLoaded())
<label for="lastip" class="control-label">Last IP:</label>
<input type="text" class="form-control" id="lastip" name="lastip"
autocomplete="off"
maxlength="10" value="<?php echo longToIp($player->getLastIP()); ?>"
maxlength="10" value="<?php
if (strlen($player->getLastIP()) > 11) {
echo inet_ntop($player->getLastIP());
}
else {
echo longToIp($player->getLastIP());
}
?>"
readonly/>
</div>
</div>
@@ -777,7 +784,7 @@ else if ($id > 0 && isset($player) && $player->isLoaded())
<div class="box-body">
<form action="<?php echo $base; ?>" method="post">
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="search_name" value="<?php echo $search_name; ?>"
<input type="text" class="form-control" name="search_name" value="<?php echo escapeHtml($search_name); ?>"
maxlength="32" size="32">
<span class="input-group-btn">
<button type="submit" type="button" class="btn btn-info btn-flat">Search</button>

View File

@@ -36,4 +36,3 @@ $twig->display('admin.statistics.html.twig', array(
'account_type' => (USE_ACCOUNT_NAME ? 'name' : 'number'),
'points' => $points
));
?>

View File

@@ -47,4 +47,3 @@ function version_revert($version)
$release = $version;
return $major . '.' . $minor . '.' . $release;
}*/
?>

View File

@@ -1,4 +1,6 @@
<?php
define('MYAAC_ADMIN', true);
require '../../common.php';
require SYSTEM . 'functions.php';
require SYSTEM . 'init.php';
@@ -11,4 +13,3 @@ if(!function_exists('phpinfo'))
die('phpinfo() disabled on this web server.');
phpinfo();
?>

View File

@@ -1,4 +1,6 @@
<?php
define('MYAAC_ADMIN', true);
require '../../common.php';
require SYSTEM . 'init.php';
require SYSTEM . 'functions.php';

View File

@@ -23,10 +23,10 @@
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
if (version_compare(phpversion(), '5.6', '<')) die('PHP version 5.6 or higher is required.');
if (version_compare(phpversion(), '7.2.5', '<')) die('PHP version 7.2.5 or higher is required.');
define('MYAAC', true);
define('MYAAC_VERSION', '0.8.7');
define('MYAAC_VERSION', '0.8.17');
define('DATABASE_VERSION', 33);
define('TABLE_PREFIX', 'myaac_');
define('START_TIME', microtime(true));
@@ -85,8 +85,10 @@ define('TFS_03', 4);
define('TFS_FIRST', TFS_02);
define('TFS_LAST', TFS_03);
session_save_path(SYSTEM . 'php_sessions');
session_start();
if (!IS_CLI) {
session_save_path(SYSTEM . 'php_sessions');
session_start();
}
// basedir
$basedir = '';
@@ -95,9 +97,13 @@ $size = count($tmp) - 1;
for($i = 1; $i < $size; $i++)
$basedir .= '/' . $tmp[$i];
$basedir = str_replace(array('/admin', '/install'), '', $basedir);
$basedir = str_replace(array('/admin', '/install', '/tools'), '', $basedir);
define('BASE_DIR', $basedir);
if (file_exists(BASE . 'config.local.php') && !defined('MYAAC_INSTALL')) {
require BASE . 'config.local.php';
}
if(!IS_CLI) {
if (isset($_SERVER['HTTP_HOST'][0])) {
$baseHost = $_SERVER['HTTP_HOST'];
@@ -114,7 +120,8 @@ if(!IS_CLI) {
define('ADMIN_URL', SERVER_URL . BASE_DIR . '/admin/');
//define('CURRENT_URL', BASE_URL . $_SERVER['REQUEST_URI']);
if(@$config['env'] === 'dev') {
require SYSTEM . 'exception.php';
}
}
require SYSTEM . 'autoload.php';

View File

@@ -93,7 +93,14 @@ $config = array(
'account_management' => true, // disable if you're using other method to manage users (fe. tfs account manager)
'account_create_auto_login' => false, // auto login after creating account?
'account_create_character_create' => true, // allow directly to create character on create account page?
'account_mail_verify' => false, // force users to confirm their email addresses when registering account
'account_mail_verify' => false, // force users to confirm their email addresses when registering
'account_mail_confirmed_reward' => [ // reward users for confirming their E-Mails
// account_mail_verify needs to be enabled too
'premium_days' => 0,
'premium_points' => 0,
'coins' => 0,
'message' => 'You received %d %s for confirming your E-Mail address.' // example: You received 20 premium points for confirming your E-Mail address.
],
'account_mail_unique' => true, // email addresses cannot be duplicated? (one account = one email)
'account_premium_days' => 0, // default premium days on new account
'account_premium_points' => 0, // default premium points on new account
@@ -260,9 +267,10 @@ $config = array(
'last_kills_limit' => 50, // max. number of deaths shown on the last kills page
// status, took automatically from config file if empty
'status_ip' => '',
'status_enabled' => true, // you can disable status checking by settings this to "false"
'status_ip' => '127.0.0.1',
'status_port' => '',
'status_timeout' => 2, // how long to wait for the initial response from the server (default: 2 seconds)
'status_timeout' => 1.0, // how long to wait for the initial response from the server (default: 1 second)
// how often to connect to server and update status (default: every minute)
// if your status timeout in config.lua is bigger, that it will be used instead

View File

View File

@@ -74,6 +74,14 @@ if((!isset($config['installed']) || !$config['installed']) && file_exists(BASE .
throw new RuntimeException('Setup detected that <b>install/</b> directory exists. Please visit <a href="' . BASE_URL . 'install">this</a> url to start MyAAC Installation.<br/>Delete <b>install/</b> directory if you already installed MyAAC.<br/>Remember to REFRESH this page when you\'re done!');
}
require_once SYSTEM . 'init.php';
require_once SYSTEM . 'template.php';
// verify myaac tables exists in database
if(!$db->hasTable('myaac_account_actions')) {
throw new RuntimeException('Seems that the table <strong>myaac_account_actions</strong> of MyAAC doesn\'t exist in the database. This is a fatal error. You can try to reinstall MyAAC by visiting <a href="' . BASE_URL . 'install">this</a> url.');
}
$found = false;
if(empty($uri) || isset($_REQUEST['template'])) {
$_REQUEST['p'] = 'news';
@@ -81,7 +89,11 @@ if(empty($uri) || isset($_REQUEST['template'])) {
}
else {
$tmp = strtolower($uri);
if(!preg_match('/[^A-z0-9_\-]/', $uri) && file_exists(SYSTEM . 'pages/' . $tmp . '.php')) {
if (!preg_match('/[^A-z0-9_\-]/', $uri) && file_exists(TEMPLATES . $template_name . '/pages/' . $tmp . '.php')) {
$_REQUEST['p'] = $uri;
$found = true;
}
else if (!preg_match('/[^A-z0-9_\-]/', $uri) && file_exists(SYSTEM . 'pages/' . $tmp . '.php')) {
$_REQUEST['p'] = $uri;
$found = true;
}
@@ -133,13 +145,13 @@ else {
'/^houses\/view\/?$/' => array('subtopic' => 'houses', 'page' => 'view')
);
foreach($rules as $rule => $redirect) {
foreach ($rules as $rule => $redirect) {
if (preg_match($rule, $uri)) {
$tmp = explode('/', $uri);
/* @var $redirect array */
foreach($redirect as $key => $value) {
foreach ($redirect as $key => $value) {
if(strpos($value, '$') !== false) {
if (strpos($value, '$') !== false) {
$value = str_replace('$' . $value[1], $tmp[$value[1]], $value);
}
@@ -154,6 +166,12 @@ else {
}
}
// handle ?fbclid=x, etc. (show news page)
if (!$found && count($_GET) > 0 && !isset($_REQUEST['subtopic']) && !isset($_REQUEST['p']) && !in_array($_SERVER['QUERY_STRING'], getDatabasePages())) {
$_REQUEST['p'] = $_REQUEST['subtopic'] = 'news';
$found = true;
}
// define page visited, so it can be used within events system
$page = isset($_REQUEST['subtopic']) ? $_REQUEST['subtopic'] : (isset($_REQUEST['p']) ? $_REQUEST['p'] : '');
if(empty($page) || !preg_match('/^[A-z0-9\_\-]+$/', $page)) {
@@ -174,24 +192,16 @@ define('PAGE', $page);
$template_place_holders = array();
require_once SYSTEM . 'init.php';
// event system
require_once SYSTEM . 'hooks.php';
$hooks = new Hooks();
$hooks->load();
require_once SYSTEM . 'template.php';
require_once SYSTEM . 'login.php';
require_once SYSTEM . 'status.php';
$twig->addGlobal('config', $config);
$twig->addGlobal('status', $status);
// verify myaac tables exists in database
if(!$db->hasTable('myaac_account_actions')) {
throw new RuntimeException('Seems that the table <strong>myaac_account_actions</strong> of MyAAC doesn\'t exist in the database. This is a fatal error. You can try to reinstall MyAAC by visiting <a href="' . BASE_URL . 'install">this</a> url.');
}
require SYSTEM . 'migrate.php';
$hooks->trigger(HOOK_STARTUP);
@@ -288,6 +298,7 @@ if($config['backward_support']) {
$config['site'] = &$config;
$config['server'] = &$config['lua'];
$config['site']['shop_system'] = $config['gifts_system'];
$config['site']['gallery_page'] = true;
if(!isset($config['vdarkborder']))
$config['vdarkborder'] = '#505050';
@@ -312,8 +323,10 @@ if($load_it)
if(SITE_CLOSED && admin())
$content .= '<p class="note">Site is under maintenance (closed mode). Only privileged users can see it.</p>';
if($config['backward_support'])
require SYSTEM . 'compat_pages.php';
if($config['backward_support']) {
require SYSTEM . 'compat/pages.php';
require SYSTEM . 'compat/classes.php';
}
$ignore = false;
@@ -333,13 +346,15 @@ if($load_it)
)) . $content;
}
} else {
$file = SYSTEM . 'pages/' . $page . '.php';
if(!@file_exists($file) || preg_match('/[^A-z0-9_\-]/', $page))
{
$file = TEMPLATES . "$template_name/pages/$page.php";
if(!@file_exists($file) || preg_match('/[^A-z0-9_\-]/', $page)) {
$file = SYSTEM . "pages/$page.php";
if(!@file_exists($file) || preg_match('/[^A-z0-9_\-]/', $page)) {
$page = '404';
$file = SYSTEM . 'pages/404.php';
}
}
}
ob_start();
if($hooks->trigger(HOOK_BEFORE_PAGE)) {

View File

@@ -38,4 +38,3 @@ if(!isset($error) || !$error) {
$error = true;
}
}
?>

View File

@@ -1,11 +1,11 @@
We have detected that you don't have access to write to the system/cache directory. Under linux you can fix it by using this two command, where first one should be enough (for apache):<br/><br/><span class="console">chown -R www-data.www-data /var/www/*</span><br/><span class="console">chmod -R 660 system/cache</span>
We have detected that you don't have access to write to the system/cache directory. Under linux you can fix it by using this two command, where first one should be enough (for apache):<br/><br/><span class="console">chown -R www-data.www-data /var/www/*</span><br/><span class="console">chmod -R 760 system/cache</span>
<style type="text/css">
.console {
font-family:Courier;
font-family: Courier,serif;
color: #CCCCCC;
background: #000000;
border: 3px double #CCCCCC;
padding: 0px;
padding: 0;
}
</style>

View File

@@ -70,7 +70,7 @@ if($step == 'database') {
$key = str_replace('var_', '', $key);
if(in_array($key, array('account', 'password', 'email', 'player_name'))) {
if(in_array($key, array('account', 'account_id', 'password', 'email', 'player_name'))) {
continue;
}
@@ -114,14 +114,12 @@ if($step == 'database') {
}
}
else if($step == 'admin') {
$config_failed = true;
if(file_exists(BASE . 'config.local.php') && isset($config['installed']) && $config['installed'] && isset($_SESSION['saved'])) {
$config_failed = false;
}
if($config_failed) {
if(!file_exists(BASE . 'config.local.php') || !isset($config['installed']) || !$config['installed']) {
$step = 'database';
}
else {
$_SESSION['saved'] = true;
}
}
else if($step == 'finish') {
$email = $_SESSION['var_email'];

View File

@@ -5,4 +5,3 @@ $twig->display('install.license.html.twig', array(
'license' => file_get_contents(BASE . 'LICENSE'),
'buttons' => next_buttons()
));
?>

View File

@@ -18,4 +18,3 @@ $twig->display('install.config.html.twig', array(
'errors' => isset($errors) ? $errors : null,
'buttons' => next_buttons()
));
?>

View File

@@ -57,16 +57,35 @@ if(!$error) {
error($database_error);
}
else {
if(!$db->hasTable('accounts')) {
$tmp = str_replace('$TABLE$', 'accounts', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
if(!$db->hasTable('players')) {
$tmp = str_replace('$TABLE$', 'players', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
if(!$db->hasTable('guilds')) {
$tmp = str_replace('$TABLE$', 'guilds', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
if(!$error) {
$twig->display('install.installer.html.twig', array(
'url' => 'tools/5-database.php',
'message' => $locale['loading_spinner']
));
if(!$error) {
if(!Validator::email($_SESSION['var_mail_admin'])) {
error($locale['step_config_mail_admin_error']);
$error = true;
}
if(!Validator::email($_SESSION['var_mail_address'])) {
error($locale['step_config_mail_address_error']);
$error = true;
@@ -82,6 +101,7 @@ if(!$error) {
}
if($saved) {
success($locale['step_database_config_saved']);
if(!$error) {
$_SESSION['saved'] = true;
}
@@ -91,7 +111,7 @@ if(!$error) {
unset($_SESSION['saved']);
$locale['step_database_error_file'] = str_replace('$FILE$', '<b>' . BASE . 'config.local.php</b>', $locale['step_database_error_file']);
warning($locale['step_database_error_file'] . '<br/>
error($locale['step_database_error_file'] . '<br/>
<textarea cols="70" rows="10">' . $content . '</textarea>');
}
}
@@ -102,6 +122,6 @@ if(!$error) {
<form action="<?php echo BASE_URL; ?>install/" method="post">
<input type="hidden" name="step" id="step" value="admin" />
<?php echo next_buttons(true, $error ? false : true);
<?php echo next_buttons(true, !$error);
?>
</form>

View File

@@ -66,7 +66,6 @@ else {
$new_account->setPassword(encrypt($password));
$new_account->setEMail($email);
$new_account->unblock();
$new_account->save();
$new_account->setCustomField('created', time());
@@ -83,7 +82,7 @@ else {
if($db->hasColumn('accounts', 'group_id'))
$account_used->setCustomField('group_id', $groups->getHighestId());
if($db->hasColumn('accounts', 'type'))
$account_used->setCustomField('type', 5);
$account_used->setCustomField('type', 6);
if(!$player_db->isLoaded())
$player->setAccountId($account_used->getId());

View File

@@ -1,4 +1,6 @@
<?php
define('MYAAC_INSTALL', true);
require_once '../../common.php';
require SYSTEM . 'functions.php';
@@ -9,8 +11,10 @@ $error = false;
require BASE . 'install/includes/config.php';
ini_set('max_execution_time', 300);
@ob_end_flush();
ob_implicit_flush();
ob_end_flush();
header('X-Accel-Buffering: no');
if(!$error) {
@@ -21,24 +25,6 @@ if(!$error) {
}
}
if(!$db->hasTable('accounts')) {
$locale['step_database_error_table'] = str_replace('$TABLE$', 'accounts', $locale['step_database_error_table']);
error($locale['step_database_error_table']);
return;
}
if(!$db->hasTable('players')) {
$locale['step_database_error_table'] = str_replace('$TABLE$', 'players', $locale['step_database_error_table']);
error($locale['step_database_error_table']);
return;
}
if(!$db->hasTable('guilds')) {
$locale['step_database_error_table'] = str_replace('$TABLE$', 'guilds', $locale['step_database_error_table']);
error($locale['step_database_error_table']);
return;
}
if($db->hasTable(TABLE_PREFIX . 'account_actions')) {
$locale['step_database_error_table_exist'] = str_replace('$TABLE$', TABLE_PREFIX . 'account_actions', $locale['step_database_error_table_exist']);
warning($locale['step_database_error_table_exist']);
@@ -71,13 +57,8 @@ else {
success($locale['step_database_adding_field'] . ' accounts.key...');
}
if(!$db->hasColumn('accounts', 'blocked')) {
if(query("ALTER TABLE `accounts` ADD `blocked` TINYINT(1) NOT NULL DEFAULT FALSE COMMENT 'internal usage' AFTER `key`;"))
success($locale['step_database_adding_field'] . ' accounts.blocked...');
}
if(!$db->hasColumn('accounts', 'created')) {
if(query("ALTER TABLE `accounts` ADD `created` INT(11) NOT NULL DEFAULT 0 AFTER `" . ($db->hasColumn('accounts', 'group_id') ? 'group_id' : 'blocked') . "`;"))
if(query("ALTER TABLE `accounts` ADD `created` INT(11) NOT NULL DEFAULT 0 AFTER `" . ($db->hasColumn('accounts', 'group_id') ? 'group_id' : 'email') . "`;"))
success($locale['step_database_adding_field'] . ' accounts.created...');
}

View File

@@ -1,4 +1,6 @@
<?php
define('MYAAC_INSTALL', true);
require_once '../../common.php';
require SYSTEM . 'functions.php';
@@ -6,8 +8,10 @@ require BASE . 'install/includes/functions.php';
require BASE . 'install/includes/locale.php';
ini_set('max_execution_time', 300);
@ob_end_flush();
ob_implicit_flush();
ob_end_flush();
header('X-Accel-Buffering: no');
if(isset($config['installed']) && $config['installed'] && !isset($_SESSION['saved'])) {

View File

@@ -4,22 +4,41 @@ server {
index index.php;
server_name your-domain.com;
# increase max file upload
client_max_body_size 10M;
# this is very important, be sure its in your nginx conf - it prevents access to logs etc.
location ~ /system {
deny all;
return 404;
}
location ~ /\.ht {
location /vendor {
deny all;
return 404;
}
# block .htaccess, CHANGELOG.md, composer.json etc.
# this is to prevent finding software versions
location ~\.(ht|md|json|dist)$ {
deny all;
return 404;
}
# block git files and folders
location ~ /\.git {
return 404;
deny all;
}
location / {
try_files $uri $uri/ /index.php;
try_files $uri $uri/ /index.php?$query_string;;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_read_timeout 240;
fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
# for ubuntu 22.04+ it will be php8.1-fpm.sock
}
}

View File

@@ -1,11 +1,3 @@
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
<IfVersion < 2.4>
order allow,deny
deny from all
</IfVersion>
<IfVersion >= 2.4>
Require all denied
</IfVersion>

View File

@@ -0,0 +1,17 @@
{
"name": "EMail Confirmed Reward",
"description": "Reward users for confirming their E-Mail.",
"version": "1.0",
"author": "MyAAC Authors",
"contact": "www.my-aac.org",
"hooks": {
"mail-confirmed-reward": {
"type": "EMAIL_CONFIRMED",
"file": "plugins/email-confirmed-reward/reward.php"
}
},
"uninstall": [
"plugins/email-confirmed-reward.json",
"plugins/email-confirmed-reward"
]
}

View File

@@ -0,0 +1,33 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
$reward = config('account_mail_confirmed_reward');
$hasCoinsColumn = $db->hasColumn('accounts', 'coins');
if ($reward['coins'] > 0 && !$hasCoinsColumn) {
log_append('email_confirm_error.log', 'accounts.coins column does not exist.');
}
if (!isset($account) || !$account->isLoaded()) {
//log_append('email_confirm_error.log', 'Account not loaded.');
return;
}
if ($reward['premium_points'] > 0) {
$account->setCustomField('premium_points', (int)$account->getCustomField('premium_points') + $reward['premium_points']);
success(sprintf($reward['message'], $reward['premium_points'], 'premium points'));
}
if ($reward['coins'] > 0 && $hasCoinsColumn) {
$account->setCustomField('coins', (int)$account->getCustomField('coins') + $reward['coins']);
success(sprintf($reward['message'], $reward['coins'], 'coins'));
}
if ($reward['premium_days'] > 0) {
$account->setPremDays($account->getPremDays() + $reward['premium_days']);
$account->save();
success(sprintf($reward['message'], $reward['premium_days'], 'premium days'));
}

View File

@@ -9,6 +9,11 @@ $loader->register();
// register the base directories for the namespace prefix
$loader->addNamespace('Composer\Semver', LIBS . 'semver');
$loader->addNamespace('Twig', LIBS . 'Twig');
$loader->addNamespace('Symfony\Polyfill\Mbstring', LIBS . 'polyfill-mbstring');
// load polyfill-mbstring bootstrap
require LIBS . 'polyfill-mbstring/bootstrap.php';
/**
* An example of a general-purpose implementation that includes the optional
* functionality of allowing multiple base directories for a single namespace

View File

@@ -76,11 +76,13 @@ $config['clients'] = [
1096,
1097,
1098,
1100,
1102,
1140,
1150,
1180,
1200,
1202,
1215,
@@ -89,4 +91,18 @@ $config['clients'] = [
1240,
1251,
1260,
1270,
1280,
1285,
1286,
1290,
1291,
1300,
1310,
1311,
1312,
1316,
1320,
1321,
];

38
system/compat/classes.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
/**
* Compat classes (backward support for Gesior AAC)
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2022 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
class Account extends OTS_Account {
public function loadById($id) {
$this->load($id);
}
public function loadByName($name) {
$this->find($name);
}
}
class Player extends OTS_Player {
public function loadById($id) {
$this->load($id);
}
public function loadByName($name) {
$this->find($name);
}
}
class Guild extends OTS_Guild {
public function loadById($id) {
$this->load($id);
}
public function loadByName($name) {
$this->find($name);
}
}
class GuildRank extends OTS_GuildRank {}
class House extends OTS_House {}

View File

@@ -10,6 +10,14 @@
defined('MYAAC') or die('Direct access not allowed!');
switch($page)
{
case 'adminpanel':
header('Location: ' . ADMIN_URL);
die;
case 'archive':
$page = 'newsarchive';
break;
case 'whoisonline':
$page = 'online';
break;
@@ -37,4 +45,3 @@ switch($page)
default:
break;
}
?>

View File

@@ -51,4 +51,3 @@ else
updateDatabaseConfig('views_counter', $views_counter); // update counter
}
}
?>

View File

@@ -116,5 +116,4 @@ defined('MYAAC') or die('Direct access not allowed!');
'<li>MySQL is not configured propertly in <i>config.lua</i>.</li>' .
'<li>MySQL server is not running.</li>' .
'</ul>' . $error->getMessage());
}

View File

@@ -23,6 +23,8 @@ function exception_handler($exception) {
$backtrace_formatted = nl2br($exception->getTraceAsString());
$message = $message . "<br/><br/>File: {$exception->getFile()}<br/>Line: {$exception->getLine()}";
// display basic error message without template
// template is missing, why? probably someone deleted templates dir, or it wasn't downloaded right
$template_file = SYSTEM . 'templates/exception.html.twig';
@@ -39,7 +41,7 @@ function exception_handler($exception) {
// we just replace some values manually
// cause in case Twig throws exception, we can show it too
$content = file_get_contents($template_file);
$content = str_replace(array('{{ BASE_URL }}', '{{ message }}', '{{ backtrace }}', '{{ powered_by }}'), array(BASE_URL, $message, $backtrace_formatted, base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4=')), $content);
$content = str_replace(array('{{ BASE_URL }}', '{{ exceptionClass }}', '{{ message }}', '{{ backtrace }}', '{{ powered_by }}'), array(BASE_URL, get_class($exception), $message, $backtrace_formatted, base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4=')), $content);
echo $content;
}

View File

@@ -7,11 +7,10 @@
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
use Twig\Loader\ArrayLoader as Twig_ArrayLoader;
defined('MYAAC') or die('Direct access not allowed!');
function message($message, $type, $return)
{
if(IS_CLI) {
@@ -757,10 +756,10 @@ function get_browser_languages()
{
$ret = array();
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
if(!isset($acceptLang[0]))
if(empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
return $ret;
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
$languages = strtolower($acceptLang);
// $languages = 'pl,en-us;q=0.7,en;q=0.3 ';
// need to remove spaces from strings to avoid error
@@ -798,7 +797,7 @@ function get_plugins()
$ret = array();
$path = PLUGINS;
foreach(scandir($path, 0) as $file) {
foreach(scandir($path, SCANDIR_SORT_ASCENDING) as $file) {
$file_ext = pathinfo($file, PATHINFO_EXTENSION);
$file_name = pathinfo($file, PATHINFO_FILENAME);
if ($file === '.' || $file === '..' || $file === 'disabled' || $file === 'example.json' || $file_ext !== 'json' || is_dir($path . $file))
@@ -924,8 +923,8 @@ function load_config_lua($filename)
$config_file = $filename;
if(!@file_exists($config_file))
{
log_append('error.log', '[load_config_file] Fatal error: Cannot load config.lua (' . $filename . '). Error: ' . print_r(error_get_last(), true));
throw new RuntimeException('ERROR: Cannot find ' . $filename . ' file. More info in system/logs/error.log');
log_append('error.log', '[load_config_file] Fatal error: Cannot load config.lua (' . $filename . ').');
throw new RuntimeException('ERROR: Cannot find ' . $filename . ' file.');
}
$result = array();
@@ -1042,7 +1041,7 @@ function getTopPlayers($limit = 5) {
$deleted = 'deletion';
$is_tfs10 = $db->hasTable('players_online');
$players = $db->query('SELECT `id`, `name`, `level`, `experience`, `looktype`' . ($db->hasColumn('players', 'lookaddons') ? ', `lookaddons`' : '') . ', `lookhead`, `lookbody`, `looklegs`, `lookfeet`' . ($is_tfs10 ? '' : ', `online`') . ' FROM `players` WHERE `group_id` < ' . config('highscores_groups_hidden') . ' AND `id` NOT IN (' . implode(', ', config('highscores_ids_hidden')) . ') AND `' . $deleted . '` = 0 AND `account_id` != 1 ORDER BY `experience` DESC LIMIT ' . (int)$limit)->fetchAll();
$players = $db->query('SELECT `id`, `name`, `level`, `vocation`, `experience`, `looktype`' . ($db->hasColumn('players', 'lookaddons') ? ', `lookaddons`' : '') . ', `lookhead`, `lookbody`, `looklegs`, `lookfeet`' . ($is_tfs10 ? '' : ', `online`') . ' FROM `players` WHERE `group_id` < ' . config('highscores_groups_hidden') . ' AND `id` NOT IN (' . implode(', ', config('highscores_ids_hidden')) . ') AND `' . $deleted . '` = 0 AND `account_id` != 1 ORDER BY `experience` DESC LIMIT ' . (int)$limit)->fetchAll();
if($is_tfs10) {
foreach($players as &$player) {
@@ -1147,9 +1146,30 @@ function clearCache()
if ($cache->fetch('failed_logins', $tmp))
$cache->delete('failed_logins');
global $template_name;
if ($cache->fetch('template_ini' . $template_name, $tmp))
$cache->delete('template_ini' . $template_name);
foreach (get_templates() as $template) {
if ($cache->fetch('template_ini_' . $template, $tmp)) {
$cache->delete('template_ini_' . $template);
}
}
if ($cache->fetch('template_menus', $tmp)) {
$cache->delete('template_menus');
}
if ($cache->fetch('database_tables', $tmp)) {
$cache->delete('database_tables');
}
if ($cache->fetch('database_columns', $tmp)) {
$cache->delete('database_columns');
}
if ($cache->fetch('database_checksum', $tmp)) {
$cache->delete('database_checksum');
}
if ($cache->fetch('hooks', $tmp)) {
$cache->delete('hooks');
}
if ($cache->fetch('last_kills', $tmp)) {
$cache->delete('last_kills');
}
}
deleteDirectory(CACHE . 'signatures', ['index.html'], true);
@@ -1244,6 +1264,40 @@ function getCustomPage($page, &$success)
return $content;
}
function escapeHtml($html) {
return htmlspecialchars($html);
}
function displayErrorBoxWithBackButton($errors, $action = null) {
global $twig;
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twig->display('account.back_button.html.twig', [
'action' => $action ?: getLink('')
]);
}
function getDatabasePages($withHidden = false): array
{
global $db, $logged_access;
if (!isset($logged_access)) {
$logged_access = 1;
}
$pages = $db->query('SELECT `name` FROM ' . TABLE_PREFIX . 'pages WHERE ' . ($withHidden ? '' : '`hidden` != 1 AND ') . '`access` <= ' . $db->quote($logged_access));
$ret = [];
if ($pages->rowCount() < 1) {
return $ret;
}
foreach($pages->fetchAll() as $page) {
$ret[] = $page['name'];
}
return $ret;
}
// validator functions
require_once LIBS . 'validator.php';
require_once SYSTEM . 'compat.php';
require_once SYSTEM . 'compat/base.php';

View File

@@ -1,4 +1,6 @@
<?php
require __DIR__ . '/../common.php';
echo MYAAC_VERSION;
if (IS_CLI) {
echo MYAAC_VERSION;
}

View File

@@ -9,42 +9,49 @@
*/
defined('MYAAC') or die('Direct access not allowed!');
define('HOOK_STARTUP', 1);
define('HOOK_BEFORE_PAGE', 2);
define('HOOK_AFTER_PAGE', 3);
define('HOOK_FINISH', 4);
define('HOOK_TIBIACOM_ARTICLE', 5);
define('HOOK_TIBIACOM_BORDER_3', 6);
define('HOOK_CHARACTERS_BEFORE_INFORMATIONS', 7);
define('HOOK_CHARACTERS_AFTER_INFORMATIONS', 8);
define('HOOK_CHARACTERS_BEFORE_SIGNATURE', 9);
define('HOOK_CHARACTERS_AFTER_SIGNATURE', 10);
define('HOOK_CHARACTERS_AFTER_ACCOUNT', 11);
define('HOOK_CHARACTERS_AFTER_CHARACTERS', 12);
define('HOOK_LOGIN', 13);
define('HOOK_LOGIN_ATTEMPT', 14);
define('HOOK_LOGOUT', 15);
define('HOOK_ACCOUNT_CREATE_BEFORE_FORM', 16);
define('HOOK_ACCOUNT_CREATE_BEFORE_BOXES', 17);
define('HOOK_ACCOUNT_CREATE_BETWEEN_BOXES_1', 18);
define('HOOK_ACCOUNT_CREATE_BETWEEN_BOXES_2', 19);
define('HOOK_ACCOUNT_CREATE_AFTER_BOXES', 20);
define('HOOK_ACCOUNT_CREATE_BEFORE_ACCOUNT', 21);
define('HOOK_ACCOUNT_CREATE_AFTER_ACCOUNT', 22);
define('HOOK_ACCOUNT_CREATE_AFTER_EMAIL', 23);
define('HOOK_ACCOUNT_CREATE_AFTER_COUNTRY', 24);
define('HOOK_ACCOUNT_CREATE_AFTER_PASSWORDS', 25);
define('HOOK_ACCOUNT_CREATE_AFTER_RECAPTCHA', 26);
define('HOOK_ACCOUNT_CREATE_BEFORE_CHARACTER_NAME', 27);
define('HOOK_ACCOUNT_CREATE_AFTER_CHARACTER_NAME', 28);
define('HOOK_ACCOUNT_CREATE_AFTER_SEX', 29);
define('HOOK_ACCOUNT_CREATE_AFTER_VOCATION', 30);
define('HOOK_ACCOUNT_CREATE_AFTER_TOWNS', 31);
define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', 32);
define('HOOK_ACCOUNT_CREATE_AFTER_FORM', 33);
define('HOOK_ACCOUNT_CREATE_AFTER_SUBMIT', 34);
$i = 0;
define('HOOK_STARTUP', ++$i);
define('HOOK_BEFORE_PAGE', ++$i);
define('HOOK_AFTER_PAGE', ++$i);
define('HOOK_FINISH', ++$i);
define('HOOK_TIBIACOM_ARTICLE', ++$i);
define('HOOK_TIBIACOM_BORDER_3', ++$i);
define('HOOK_CHARACTERS_BEFORE_INFORMATIONS', ++$i);
define('HOOK_CHARACTERS_AFTER_INFORMATIONS', ++$i);
define('HOOK_CHARACTERS_BEFORE_SKILLS', ++$i);
define('HOOK_CHARACTERS_AFTER_SKILLS', ++$i);
define('HOOK_CHARACTERS_AFTER_QUESTS', ++$i);
define('HOOK_CHARACTERS_AFTER_EQUIPMENT', ++$i);
define('HOOK_CHARACTERS_BEFORE_DEATHS', ++$i);
define('HOOK_CHARACTERS_BEFORE_SIGNATURE', ++$i);
define('HOOK_CHARACTERS_AFTER_SIGNATURE', ++$i);
define('HOOK_CHARACTERS_AFTER_ACCOUNT', ++$i);
define('HOOK_CHARACTERS_AFTER_CHARACTERS', ++$i);
define('HOOK_LOGIN', ++$i);
define('HOOK_LOGIN_ATTEMPT', ++$i);
define('HOOK_LOGOUT', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_FORM', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_BOXES', ++$i);
define('HOOK_ACCOUNT_CREATE_BETWEEN_BOXES_1', ++$i);
define('HOOK_ACCOUNT_CREATE_BETWEEN_BOXES_2', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_BOXES', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_ACCOUNT', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_ACCOUNT', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_EMAIL', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_COUNTRY', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_PASSWORDS', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_RECAPTCHA', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_CHARACTER_NAME', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_CHARACTER_NAME', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_SEX', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_VOCATION', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_TOWNS', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_FORM', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_SUBMIT', ++$i);
define('HOOK_EMAIL_CONFIRMED', ++$i);
define('HOOK_FIRST', HOOK_STARTUP);
define('HOOK_LAST', HOOK_ACCOUNT_CREATE_AFTER_SUBMIT);
define('HOOK_LAST', HOOK_EMAIL_CONFIRMED);
require_once LIBS . 'plugins.php';
class Hook
@@ -67,9 +74,7 @@ class Hook
}*/
global $db, $config, $template_path, $ots, $content, $twig;
if(file_exists(BASE . $this->_file)) {
$ret = require BASE . $this->_file;
}
$ret = include BASE . $this->_file;
return !isset($ret) || $ret == 1 || $ret;
}

View File

@@ -31,9 +31,6 @@ if($config['gzip_output'] && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($
require_once SYSTEM . 'libs/cache.php';
$cache = Cache::getInstance();
// twig
require_once SYSTEM . 'twig.php';
// trim values we receive
if(isset($_POST))
{
@@ -114,7 +111,7 @@ if(!isset($foundValue)) {
$config['data_path'] = $foundValue;
unset($foundValue);
// new config values for compability
// new config values for compatibility
if(!isset($config['highscores_ids_hidden']) || count($config['highscores_ids_hidden']) == 0) {
$config['highscores_ids_hidden'] = array(0);
}
@@ -126,6 +123,9 @@ require_once SYSTEM . 'libs/pot/OTS.php';
$ots = POT::getInstance();
require_once SYSTEM . 'database.php';
// twig
require_once SYSTEM . 'twig.php';
define('USE_ACCOUNT_NAME', $db->hasColumn('accounts', 'name'));
// load vocation names
$tmp = '';

View File

@@ -58,4 +58,3 @@ function outputItem($id = 100, $count = 1)
$file_name = Items_Images::$outputDir . $file_name . '.gif';
readfile($file_name);
}
?>

View File

@@ -138,7 +138,7 @@ class CreateCharacter
if(empty($errors))
{
$number_of_players_on_account = $account->getPlayersList(false)->count();
$number_of_players_on_account = $account->getPlayersList(true)->count();
if($number_of_players_on_account >= config('characters_per_account'))
$errors[] = 'You have too many characters on your account <b>('.$number_of_players_on_account.'/'.config('characters_per_account').')</b>!';
}
@@ -252,11 +252,13 @@ class CreateCharacter
}
}
if ($db->hasTable('player_items') && $db->hasColumn('player_items', 'pid') && $db->hasColumn('player_items', 'sid') && $db->hasColumn('player_items', 'itemtype')) {
$loaded_items_to_copy = $db->query("SELECT * FROM player_items WHERE player_id = ".$char_to_copy->getId()."");
foreach($loaded_items_to_copy as $save_item) {
$blob = $db->quote($save_item['attributes']);
$db->query("INSERT INTO `player_items` (`player_id` ,`pid` ,`sid` ,`itemtype`, `count`, `attributes`) VALUES ('".$player->getId()."', '".$save_item['pid']."', '".$save_item['sid']."', '".$save_item['itemtype']."', '".$save_item['count']."', $blob);");
}
}
global $twig;
$twig->display('success.html.twig', array(

View File

@@ -18,7 +18,7 @@ namespace Twig\Cache;
*/
class FilesystemCache implements CacheInterface
{
const FORCE_BYTECODE_INVALIDATION = 1;
public const FORCE_BYTECODE_INVALIDATION = 1;
private $directory;
private $options;
@@ -35,7 +35,7 @@ class FilesystemCache implements CacheInterface
public function generateKey($name, $className)
{
$hash = hash('sha256', $className);
$hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className);
return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php';
}
@@ -67,7 +67,7 @@ class FilesystemCache implements CacheInterface
if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {
// Compile cached file into bytecode cache
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
@opcache_invalidate($key, true);
} elseif (\function_exists('apc_compile_file')) {
apc_compile_file($key);

View File

@@ -14,11 +14,9 @@ namespace Twig\Cache;
/**
* Implements a no-cache strategy.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NullCache implements CacheInterface
final class NullCache implements CacheInterface
{
public function generateKey($name, $className)
{

View File

@@ -12,23 +12,22 @@
namespace Twig;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
/**
* Compiles a node to PHP code.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Compiler implements \Twig_CompilerInterface
class Compiler
{
protected $lastLine;
protected $source;
protected $indentation;
protected $env;
protected $debugInfo = [];
protected $sourceOffset;
protected $sourceLine;
protected $filename;
private $lastLine;
private $source;
private $indentation;
private $env;
private $debugInfo = [];
private $sourceOffset;
private $sourceLine;
private $varNameSalt = 0;
public function __construct(Environment $env)
@@ -36,16 +35,6 @@ class Compiler implements \Twig_CompilerInterface
$this->env = $env;
}
/**
* @deprecated since 1.25 (to be removed in 2.0)
*/
public function getFilename()
{
@trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Returns the environment instance related to this compiler.
*
@@ -73,7 +62,7 @@ class Compiler implements \Twig_CompilerInterface
*
* @return $this
*/
public function compile(\Twig_NodeInterface $node, $indentation = 0)
public function compile(Node $node, $indentation = 0)
{
$this->lastLine = null;
$this->source = '';
@@ -84,17 +73,12 @@ class Compiler implements \Twig_CompilerInterface
$this->indentation = $indentation;
$this->varNameSalt = 0;
if ($node instanceof ModuleNode) {
// to be removed in 2.0
$this->filename = $node->getTemplateName();
}
$node->compile($this);
return $this;
}
public function subcompile(\Twig_NodeInterface $node, $raw = true)
public function subcompile(Node $node, $raw = true)
{
if (false === $raw) {
$this->source .= str_repeat(' ', $this->indentation * 4);
@@ -124,9 +108,8 @@ class Compiler implements \Twig_CompilerInterface
*
* @return $this
*/
public function write()
public function write(...$strings)
{
$strings = \func_get_args();
foreach ($strings as $string) {
$this->source .= str_repeat(' ', $this->indentation * 4).$string;
}
@@ -134,22 +117,6 @@ class Compiler implements \Twig_CompilerInterface
return $this;
}
/**
* Appends an indentation to the current PHP code after compilation.
*
* @return $this
*
* @deprecated since 1.27 (to be removed in 2.0).
*/
public function addIndentation()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use write(\'\') instead.', E_USER_DEPRECATED);
$this->source .= str_repeat(' ', $this->indentation * 4);
return $this;
}
/**
* Adds a quoted string to the compiled code.
*
@@ -174,21 +141,21 @@ class Compiler implements \Twig_CompilerInterface
public function repr($value)
{
if (\is_int($value) || \is_float($value)) {
if (false !== $locale = setlocale(LC_NUMERIC, '0')) {
setlocale(LC_NUMERIC, 'C');
if (false !== $locale = setlocale(\LC_NUMERIC, '0')) {
setlocale(\LC_NUMERIC, 'C');
}
$this->raw(var_export($value, true));
if (false !== $locale) {
setlocale(LC_NUMERIC, $locale);
setlocale(\LC_NUMERIC, $locale);
}
} elseif (null === $value) {
$this->raw('null');
} elseif (\is_bool($value)) {
$this->raw($value ? 'true' : 'false');
} elseif (\is_array($value)) {
$this->raw('[');
$this->raw('array(');
$first = true;
foreach ($value as $key => $v) {
if (!$first) {
@@ -199,7 +166,7 @@ class Compiler implements \Twig_CompilerInterface
$this->raw(' => ');
$this->repr($v);
}
$this->raw(']');
$this->raw(')');
} else {
$this->string($value);
}
@@ -212,22 +179,12 @@ class Compiler implements \Twig_CompilerInterface
*
* @return $this
*/
public function addDebugInfo(\Twig_NodeInterface $node)
public function addDebugInfo(Node $node)
{
if ($node->getTemplateLine() != $this->lastLine) {
$this->write(sprintf("// line %d\n", $node->getTemplateLine()));
// when mbstring.func_overload is set to 2
// mb_substr_count() replaces substr_count()
// but they have different signatures!
if (((int) ini_get('mbstring.func_overload')) & 2) {
@trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED);
// this is much slower than the "right" version
$this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n");
} else {
$this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
}
$this->sourceOffset = \strlen($this->source);
$this->debugInfo[$this->sourceLine] = $node->getTemplateLine();
@@ -281,7 +238,7 @@ class Compiler implements \Twig_CompilerInterface
public function getVarName()
{
return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++));
return sprintf('__internal_compile_%d', $this->varNameSalt++);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -38,11 +38,9 @@ use Twig\Template;
*/
class Error extends \Exception
{
protected $lineno;
// to be renamed to name in 2.0
protected $filename;
protected $rawMessage;
private $lineno;
private $name;
private $rawMessage;
private $sourcePath;
private $sourceCode;
@@ -57,22 +55,23 @@ class Error extends \Exception
* @param Source|string|null $source The source context where the error occurred
* @param \Exception $previous The previous exception
*/
public function __construct($message, $lineno = -1, $source = null, \Exception $previous = null)
public function __construct(string $message, int $lineno = -1, $source = null, \Exception $previous = null)
{
parent::__construct('', 0, $previous);
if (null === $source) {
$name = null;
} elseif (!$source instanceof Source) {
// for compat with the Twig C ext., passing the template name as string is accepted
} elseif (!$source instanceof Source && !$source instanceof \Twig_Source) {
@trigger_error(sprintf('Passing a string as a source to %s is deprecated since Twig 2.6.1; pass a Twig\Source instance instead.', __CLASS__), \E_USER_DEPRECATED);
$name = $source;
} else {
$name = $source->getName();
$this->sourceCode = $source->getCode();
$this->sourcePath = $source->getPath();
}
parent::__construct('', 0, $previous);
$this->lineno = $lineno;
$this->filename = $name;
$this->name = $name;
$this->rawMessage = $message;
$this->updateRepr();
}
@@ -87,67 +86,6 @@ class Error extends \Exception
return $this->rawMessage;
}
/**
* Gets the logical name where the error occurred.
*
* @return string The name
*
* @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead.
*/
public function getTemplateFile()
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Sets the logical name where the error occurred.
*
* @param string $name The name
*
* @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead.
*/
public function setTemplateFile($name)
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
$this->filename = $name;
$this->updateRepr();
}
/**
* Gets the logical name where the error occurred.
*
* @return string The name
*
* @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead.
*/
public function getTemplateName()
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Sets the logical name where the error occurred.
*
* @param string $name The name
*
* @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead.
*/
public function setTemplateName($name)
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
$this->filename = $name;
$this->sourceCode = $this->sourcePath = null;
$this->updateRepr();
}
/**
* Gets the template line where the error occurred.
*
@@ -177,7 +115,7 @@ class Error extends \Exception
*/
public function getSourceContext()
{
return $this->filename ? new Source($this->sourceCode, $this->filename, $this->sourcePath) : null;
return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null;
}
/**
@@ -186,10 +124,10 @@ class Error extends \Exception
public function setSourceContext(Source $source = null)
{
if (null === $source) {
$this->sourceCode = $this->filename = $this->sourcePath = null;
$this->sourceCode = $this->name = $this->sourcePath = null;
} else {
$this->sourceCode = $source->getCode();
$this->filename = $source->getName();
$this->name = $source->getName();
$this->sourcePath = $source->getPath();
}
@@ -208,10 +146,7 @@ class Error extends \Exception
$this->updateRepr();
}
/**
* @internal
*/
protected function updateRepr()
private function updateRepr()
{
$this->message = $this->rawMessage;
@@ -234,11 +169,11 @@ class Error extends \Exception
$questionMark = true;
}
if ($this->filename) {
if (\is_string($this->filename) || (\is_object($this->filename) && method_exists($this->filename, '__toString'))) {
$name = sprintf('"%s"', $this->filename);
if ($this->name) {
if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) {
$name = sprintf('"%s"', $this->name);
} else {
$name = json_encode($this->filename);
$name = json_encode($this->name);
}
$this->message .= sprintf(' in %s', $name);
}
@@ -256,20 +191,17 @@ class Error extends \Exception
}
}
/**
* @internal
*/
protected function guessTemplateInfo()
private function guessTemplateInfo()
{
$template = null;
$templateClass = null;
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
$backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig_Template' !== \get_class($trace['object'])) {
if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig\Template' !== \get_class($trace['object'])) {
$currentClass = \get_class($trace['object']);
$isEmbedContainer = 0 === strpos($templateClass, $currentClass);
if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass);
if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object'];
$templateClass = \get_class($trace['object']);
}
@@ -277,8 +209,8 @@ class Error extends \Exception
}
// update template name
if (null !== $template && null === $this->filename) {
$this->filename = $template->getTemplateName();
if (null !== $template && null === $this->name) {
$this->name = $template->getTemplateName();
}
// update template path if any
@@ -296,7 +228,7 @@ class Error extends \Exception
$file = $r->getFileName();
$exceptions = [$e = $this];
while ($e instanceof self && $e = $e->getPrevious()) {
while ($e = $e->getPrevious()) {
$exceptions[] = $e;
}

View File

@@ -26,20 +26,6 @@ class SyntaxError extends Error
* @param array $items An array of possible items
*/
public function addSuggestions($name, array $items)
{
if (!$alternatives = self::computeAlternatives($name, $items)) {
return;
}
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', $alternatives)));
}
/**
* @internal
*
* To be merged with the addSuggestions() method in 2.0.
*/
public static function computeAlternatives($name, $items)
{
$alternatives = [];
foreach ($items as $item) {
@@ -48,9 +34,14 @@ class SyntaxError extends Error
$alternatives[$item] = $lev;
}
}
if (!$alternatives) {
return;
}
asort($alternatives);
return array_keys($alternatives);
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives))));
}
}

View File

@@ -13,6 +13,7 @@
namespace Twig;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ArrowFunctionExpression;
use Twig\Node\Expression\AssignNameExpression;
@@ -24,6 +25,7 @@ use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression;
use Twig\Node\Expression\TestExpression;
use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary;
@@ -43,30 +45,20 @@ use Twig\Node\Node;
*/
class ExpressionParser
{
const OPERATOR_LEFT = 1;
const OPERATOR_RIGHT = 2;
protected $parser;
protected $unaryOperators;
protected $binaryOperators;
public const OPERATOR_LEFT = 1;
public const OPERATOR_RIGHT = 2;
private $parser;
private $env;
private $unaryOperators;
private $binaryOperators;
public function __construct(Parser $parser, $env = null)
public function __construct(Parser $parser, Environment $env)
{
$this->parser = $parser;
if ($env instanceof Environment) {
$this->env = $env;
$this->unaryOperators = $env->getUnaryOperators();
$this->binaryOperators = $env->getBinaryOperators();
} else {
@trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED);
$this->env = $parser->getEnvironment();
$this->unaryOperators = func_get_arg(1);
$this->binaryOperators = func_get_arg(2);
}
}
public function parseExpression($precedence = 0, $allowArrow = false)
@@ -86,7 +78,7 @@ class ExpressionParser
} elseif ('is' === $token->getValue()) {
$expr = $this->parseTestExpression($expr);
} elseif (isset($op['callable'])) {
$expr = \call_user_func($op['callable'], $this->parser, $expr);
$expr = $op['callable']($this->parser, $expr);
} else {
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
$class = $op['class'];
@@ -111,57 +103,57 @@ class ExpressionParser
$stream = $this->parser->getStream();
// short array syntax (one argument, no parentheses)?
if ($stream->look(1)->test(Token::ARROW_TYPE)) {
if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) {
$line = $stream->getCurrent()->getLine();
$token = $stream->expect(Token::NAME_TYPE);
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names = [new AssignNameExpression($token->getValue(), $token->getLine())];
$stream->expect(Token::ARROW_TYPE);
$stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
// first, determine if we are parsing an arrow function by finding => (long form)
$i = 0;
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) {
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
return null;
}
++$i;
while (true) {
// variable name
++$i;
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) {
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
++$i;
}
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) {
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
return null;
}
++$i;
if (!$stream->look($i)->test(Token::ARROW_TYPE)) {
if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) {
return null;
}
// yes, let's parse it properly
$token = $stream->expect(Token::PUNCTUATION_TYPE, '(');
$token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(');
$line = $token->getLine();
$names = [];
while (true) {
$token = $stream->expect(Token::NAME_TYPE);
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names[] = new AssignNameExpression($token->getValue(), $token->getLine());
if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
$stream->expect(Token::PUNCTUATION_TYPE, ')');
$stream->expect(Token::ARROW_TYPE);
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')');
$stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
protected function getPrimary()
private function getPrimary(): AbstractExpression
{
$token = $this->parser->getCurrentToken();
@@ -172,10 +164,10 @@ class ExpressionParser
$class = $operator['class'];
return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
} elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) {
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$this->parser->getStream()->next();
$expr = $this->parseExpression();
$this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
$this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed');
return $this->parsePostfixExpression($expr);
}
@@ -183,12 +175,12 @@ class ExpressionParser
return $this->parsePrimaryExpression();
}
protected function parseConditionalExpression($expr)
private function parseConditionalExpression($expr): AbstractExpression
{
while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) {
if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {
while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) {
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr2 = $this->parseExpression();
if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {
if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr3 = $this->parseExpression();
} else {
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
@@ -204,21 +196,21 @@ class ExpressionParser
return $expr;
}
protected function isUnary(Token $token)
private function isUnary(Token $token): bool
{
return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]);
}
protected function isBinary(Token $token)
private function isBinary(Token $token): bool
{
return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]);
}
public function parsePrimaryExpression()
{
$token = $this->parser->getCurrentToken();
switch ($token->getType()) {
case Token::NAME_TYPE:
case /* Token::NAME_TYPE */ 5:
$this->parser->getStream()->next();
switch ($token->getValue()) {
case 'true':
@@ -247,17 +239,17 @@ class ExpressionParser
}
break;
case Token::NUMBER_TYPE:
case /* Token::NUMBER_TYPE */ 6:
$this->parser->getStream()->next();
$node = new ConstantExpression($token->getValue(), $token->getLine());
break;
case Token::STRING_TYPE:
case Token::INTERPOLATION_START_TYPE:
case /* Token::STRING_TYPE */ 7:
case /* Token::INTERPOLATION_START_TYPE */ 10:
$node = $this->parseStringExpression();
break;
case Token::OPERATOR_TYPE:
case /* Token::OPERATOR_TYPE */ 8:
if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
@@ -267,10 +259,8 @@ class ExpressionParser
$class = $this->unaryOperators[$token->getValue()]['class'];
$ref = new \ReflectionClass($class);
$negClass = 'Twig\Node\Expression\Unary\NegUnary';
$posClass = 'Twig\Node\Expression\Unary\PosUnary';
if (!(\in_array($ref->getName(), [$negClass, $posClass, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
|| $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass)
if (!(\in_array($ref->getName(), [NegUnary::class, PosUnary::class, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
|| $ref->isSubclassOf(NegUnary::class) || $ref->isSubclassOf(PosUnary::class)
|| $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos'))
) {
throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
@@ -285,11 +275,11 @@ class ExpressionParser
// no break
default:
if ($token->test(Token::PUNCTUATION_TYPE, '[')) {
if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
$node = $this->parseArrayExpression();
} elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
$node = $this->parseHashExpression();
} elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
} elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
} else {
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
@@ -307,12 +297,12 @@ class ExpressionParser
// a string cannot be followed by another string in a single expression
$nextCanBeString = true;
while (true) {
if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) {
if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) {
$nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
$nextCanBeString = false;
} elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) {
} elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
$nodes[] = $this->parseExpression();
$stream->expect(Token::INTERPOLATION_END_TYPE);
$stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11);
$nextCanBeString = true;
} else {
break;
@@ -330,16 +320,16 @@ class ExpressionParser
public function parseArrayExpression()
{
$stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) {
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
if (!$first) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma');
// trailing ,?
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
break;
}
}
@@ -347,7 +337,7 @@ class ExpressionParser
$node->addElement($this->parseExpression());
}
$stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed');
return $node;
}
@@ -355,16 +345,16 @@ class ExpressionParser
public function parseHashExpression()
{
$stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) {
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
if (!$first) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma');
// trailing ,?
if ($stream->test(Token::PUNCTUATION_TYPE, '}')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
break;
}
}
@@ -376,9 +366,18 @@ class ExpressionParser
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if (($token = $stream->nextIf(Token::STRING_TYPE)) || ($token = $stream->nextIf(Token::NAME_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) {
if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
// {a} is a shortcut for {a:a}
if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {
$value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
$node->addElement($value, $key);
continue;
}
} elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$key = $this->parseExpression();
} else {
$current = $stream->getCurrent();
@@ -386,12 +385,12 @@ class ExpressionParser
throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
}
$stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
$value = $this->parseExpression();
$node->addElement($value, $key);
}
$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
return $node;
}
@@ -400,7 +399,7 @@ class ExpressionParser
{
while (true) {
$token = $this->parser->getCurrentToken();
if (Token::PUNCTUATION_TYPE == $token->getType()) {
if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) {
if ('.' == $token->getValue() || '[' == $token->getValue()) {
$node = $this->parseSubscriptExpression($node);
} elseif ('|' == $token->getValue()) {
@@ -474,22 +473,22 @@ class ExpressionParser
if ('.' == $token->getValue()) {
$token = $stream->next();
if (
Token::NAME_TYPE == $token->getType()
/* Token::NAME_TYPE */ 5 == $token->getType()
||
Token::NUMBER_TYPE == $token->getType()
/* Token::NUMBER_TYPE */ 6 == $token->getType()
||
(Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
(/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
) {
$arg = new ConstantExpression($token->getValue(), $lineno);
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$type = Template::METHOD_CALL;
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
}
} else {
throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext());
throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
}
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
@@ -499,11 +498,7 @@ class ExpressionParser
$name = $arg->getAttribute('value');
if ($this->parser->isReservedMacroName($name)) {
throw new SyntaxError(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext());
}
$node = new MethodCallExpression($node, 'get'.$name, $arguments, $lineno);
$node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
$node->setAttribute('safe', true);
return $node;
@@ -513,19 +508,19 @@ class ExpressionParser
// slice?
$slice = false;
if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true;
$arg = new ConstantExpression(0, $token->getLine());
} else {
$arg = $this->parseExpression();
}
if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {
if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true;
}
if ($slice) {
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
$length = new ConstantExpression(null, $token->getLine());
} else {
$length = $this->parseExpression();
@@ -535,12 +530,12 @@ class ExpressionParser
$arguments = new Node([$arg, $length]);
$filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
$stream->expect(Token::PUNCTUATION_TYPE, ']');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
return $filter;
}
$stream->expect(Token::PUNCTUATION_TYPE, ']');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
}
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
@@ -556,10 +551,10 @@ class ExpressionParser
public function parseFilterExpressionRaw($node, $tag = null)
{
while (true) {
$token = $this->parser->getStream()->expect(Token::NAME_TYPE);
$token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
$name = new ConstantExpression($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) {
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = new Node();
} else {
$arguments = $this->parseArguments(true, false, true);
@@ -569,7 +564,7 @@ class ExpressionParser
$node = new $class($node, $name, $arguments, $token->getLine(), $tag);
if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) {
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) {
break;
}
@@ -594,21 +589,26 @@ class ExpressionParser
$args = [];
$stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
if (!empty($args)) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma');
// if the comma above was a trailing comma, early exit the argument parse loop
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
break;
}
}
if ($definition) {
$token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');
$token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name');
$value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
} else {
$value = $this->parseExpression(0, $allowArrow);
}
$name = null;
if ($namedArguments && $token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) {
if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) {
if (!$value instanceof NameExpression) {
throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
}
@@ -618,7 +618,7 @@ class ExpressionParser
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext());
throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext());
}
} else {
$value = $this->parseExpression(0, $allowArrow);
@@ -639,7 +639,7 @@ class ExpressionParser
}
}
}
$stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis');
return new Node($args);
}
@@ -650,19 +650,19 @@ class ExpressionParser
$targets = [];
while (true) {
$token = $this->parser->getCurrentToken();
if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
} else {
$stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to');
$stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to');
}
$value = $token->getValue();
if (\in_array(strtolower($value), ['true', 'false', 'none', 'null'])) {
if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
}
$targets[] = new AssignNameExpression($value, $token->getLine());
if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
@@ -675,7 +675,7 @@ class ExpressionParser
$targets = [];
while (true) {
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) {
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
@@ -683,35 +683,42 @@ class ExpressionParser
return new Node($targets);
}
private function parseNotTestExpression(\Twig_NodeInterface $node)
private function parseNotTestExpression(Node $node): NotUnary
{
return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
}
private function parseTestExpression(\Twig_NodeInterface $node)
private function parseTestExpression(Node $node): TestExpression
{
$stream = $this->parser->getStream();
list($name, $test) = $this->getTest($node->getTemplateLine());
$class = $this->getTestNodeClass($test);
$arguments = null;
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = $this->parseArguments(true);
} elseif ($test->hasOneMandatoryArgument()) {
$arguments = new Node([0 => $this->parsePrimaryExpression()]);
}
if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
$node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
$node->setAttribute('safe', true);
}
return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
}
private function getTest($line)
private function getTest(int $line): array
{
$stream = $this->parser->getStream();
$name = $stream->expect(Token::NAME_TYPE)->getValue();
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
if ($test = $this->env->getTest($name)) {
return [$name, $test];
}
if ($stream->test(Token::NAME_TYPE)) {
if ($stream->test(/* Token::NAME_TYPE */ 5)) {
// try 2-words tests
$name = $name.' '.$this->parser->getCurrentToken()->getValue();
@@ -728,11 +735,12 @@ class ExpressionParser
throw $e;
}
private function getTestNodeClass($test)
private function getTestNodeClass(TwigTest $test): string
{
if ($test instanceof TwigTest && $test->isDeprecated()) {
if ($test->isDeprecated()) {
$stream = $this->parser->getStream();
$message = sprintf('Twig Test "%s" is deprecated', $test->getName());
if (!\is_bool($test->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $test->getDeprecatedVersion());
}
@@ -740,19 +748,15 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $test->getAlternative());
}
$src = $stream->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine());
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
@trigger_error($message, E_USER_DEPRECATED);
@trigger_error($message, \E_USER_DEPRECATED);
}
if ($test instanceof TwigTest) {
return $test->getNodeClass();
}
return $test instanceof \Twig_Test_Node ? $test->getClass() : 'Twig\Node\Expression\TestExpression';
}
protected function getFunctionNodeClass($name, $line)
private function getFunctionNodeClass(string $name, int $line): string
{
if (false === $function = $this->env->getFunction($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
@@ -761,7 +765,7 @@ class ExpressionParser
throw $e;
}
if ($function instanceof TwigFunction && $function->isDeprecated()) {
if ($function->isDeprecated()) {
$message = sprintf('Twig Function "%s" is deprecated', $function->getName());
if (!\is_bool($function->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $function->getDeprecatedVersion());
@@ -770,19 +774,15 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $function->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line);
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED);
@trigger_error($message, \E_USER_DEPRECATED);
}
if ($function instanceof TwigFunction) {
return $function->getNodeClass();
}
return $function instanceof \Twig_Function_Node ? $function->getClass() : 'Twig\Node\Expression\FunctionExpression';
}
protected function getFilterNodeClass($name, $line)
private function getFilterNodeClass(string $name, int $line): string
{
if (false === $filter = $this->env->getFilter($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
@@ -791,7 +791,7 @@ class ExpressionParser
throw $e;
}
if ($filter instanceof TwigFilter && $filter->isDeprecated()) {
if ($filter->isDeprecated()) {
$message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
if (!\is_bool($filter->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
@@ -800,20 +800,16 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $filter->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line);
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED);
@trigger_error($message, \E_USER_DEPRECATED);
}
if ($filter instanceof TwigFilter) {
return $filter->getNodeClass();
}
return $filter instanceof \Twig_Filter_Node ? $filter->getClass() : 'Twig\Node\Expression\FilterExpression';
}
// checks that the node only contains "constant" elements
protected function checkConstantExpression(\Twig_NodeInterface $node)
private function checkConstantExpression(Node $node): bool
{
if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
|| $node instanceof NegUnary || $node instanceof PosUnary

View File

@@ -11,17 +11,8 @@
namespace Twig\Extension;
use Twig\Environment;
abstract class AbstractExtension implements ExtensionInterface
{
/**
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead
*/
public function initRuntime(Environment $environment)
{
}
public function getTokenParsers()
{
return [];
@@ -51,22 +42,6 @@ abstract class AbstractExtension implements ExtensionInterface
{
return [];
}
/**
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead
*/
public function getGlobals()
{
return [];
}
/**
* @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
*/
public function getName()
{
return \get_class($this);
}
}
class_alias('Twig\Extension\AbstractExtension', 'Twig_Extension');

File diff suppressed because it is too large Load Diff

View File

@@ -12,10 +12,7 @@
namespace Twig\Extension {
use Twig\TwigFunction;
/**
* @final
*/
class DebugExtension extends AbstractExtension
final class DebugExtension extends AbstractExtension
{
public function getFunctions()
{
@@ -33,11 +30,6 @@ class DebugExtension extends AbstractExtension
new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
];
}
public function getName()
{
return 'debug';
}
}
class_alias('Twig\Extension\DebugExtension', 'Twig_Extension_Debug');
@@ -48,7 +40,7 @@ use Twig\Environment;
use Twig\Template;
use Twig\TemplateWrapper;
function twig_var_dump(Environment $env, $context, array $vars = [])
function twig_var_dump(Environment $env, $context, ...$vars)
{
if (!$env->isDebug()) {
return;
@@ -66,9 +58,7 @@ function twig_var_dump(Environment $env, $context, array $vars = [])
var_dump($vars);
} else {
foreach ($vars as $var) {
var_dump($var);
}
var_dump(...$vars);
}
return ob_get_clean();

View File

@@ -10,16 +10,21 @@
*/
namespace Twig\Extension {
use Twig\FileExtensionEscapingStrategy;
use Twig\NodeVisitor\EscaperNodeVisitor;
use Twig\TokenParser\AutoEscapeTokenParser;
use Twig\TwigFilter;
/**
* @final
*/
class EscaperExtension extends AbstractExtension
final class EscaperExtension extends AbstractExtension
{
protected $defaultStrategy;
private $defaultStrategy;
private $escapers = [];
/** @internal */
public $safeClasses = [];
/** @internal */
public $safeLookup = [];
/**
* @param string|false|callable $defaultStrategy An escaping strategy
@@ -44,6 +49,8 @@ class EscaperExtension extends AbstractExtension
public function getFilters()
{
return [
new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]),
];
}
@@ -58,21 +65,8 @@ class EscaperExtension extends AbstractExtension
*/
public function setDefaultStrategy($defaultStrategy)
{
// for BC
if (true === $defaultStrategy) {
@trigger_error('Using "true" as the default strategy is deprecated since version 1.21. Use "html" instead.', E_USER_DEPRECATED);
$defaultStrategy = 'html';
}
if ('filename' === $defaultStrategy) {
@trigger_error('Using "filename" as the default strategy is deprecated since version 1.27. Use "name" instead.', E_USER_DEPRECATED);
$defaultStrategy = 'name';
}
if ('name' === $defaultStrategy) {
$defaultStrategy = ['\Twig\FileExtensionEscapingStrategy', 'guess'];
$defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];
}
$this->defaultStrategy = $defaultStrategy;
@@ -96,9 +90,47 @@ class EscaperExtension extends AbstractExtension
return $this->defaultStrategy;
}
public function getName()
/**
* Defines a new escaper to be used via the escape filter.
*
* @param string $strategy The strategy name that should be used as a strategy in the escape call
* @param callable $callable A valid PHP callable
*/
public function setEscaper($strategy, callable $callable)
{
return 'escaper';
$this->escapers[$strategy] = $callable;
}
/**
* Gets all defined escapers.
*
* @return callable[] An array of escapers
*/
public function getEscapers()
{
return $this->escapers;
}
public function setSafeClasses(array $safeClasses = [])
{
$this->safeClasses = [];
$this->safeLookup = [];
foreach ($safeClasses as $class => $strategies) {
$this->addSafeClass($class, $strategies);
}
}
public function addSafeClass(string $class, array $strategies)
{
$class = ltrim($class, '\\');
if (!isset($this->safeClasses[$class])) {
$this->safeClasses[$class] = [];
}
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
foreach ($strategies as $strategy) {
$this->safeLookup[$strategy][$class] = true;
}
}
}
@@ -106,6 +138,14 @@ class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper');
}
namespace {
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\EscaperExtension;
use Twig\Markup;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Node;
/**
* Marks a variable as being safe.
*
@@ -117,4 +157,272 @@ function twig_raw_filter($string)
{
return $string;
}
/**
* Escapes a string.
*
* @param mixed $string The value to be escaped
* @param string $strategy The escaping strategy
* @param string $charset The charset
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
*
* @return string
*/
function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
{
if ($autoescape && $string instanceof Markup) {
return $string;
}
if (!\is_string($string)) {
if (\is_object($string) && method_exists($string, '__toString')) {
if ($autoescape) {
$c = \get_class($string);
$ext = $env->getExtension(EscaperExtension::class);
if (!isset($ext->safeClasses[$c])) {
$ext->safeClasses[$c] = [];
foreach (class_parents($string) + class_implements($string) as $class) {
if (isset($ext->safeClasses[$class])) {
$ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
foreach ($ext->safeClasses[$class] as $s) {
$ext->safeLookup[$s][$c] = true;
}
}
}
}
if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
return (string) $string;
}
}
$string = (string) $string;
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
return $string;
}
}
if ('' === $string) {
return '';
}
if (null === $charset) {
$charset = $env->getCharset();
}
switch ($strategy) {
case 'html':
// see https://www.php.net/htmlspecialchars
// Using a static variable to avoid initializing the array
// each time the function is called. Moving the declaration on the
// top of the function slow downs other escaping strategies.
static $htmlspecialcharsCharsets = [
'ISO-8859-1' => true, 'ISO8859-1' => true,
'ISO-8859-15' => true, 'ISO8859-15' => true,
'utf-8' => true, 'UTF-8' => true,
'CP866' => true, 'IBM866' => true, '866' => true,
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
'1251' => true,
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
'BIG5' => true, '950' => true,
'GB2312' => true, '936' => true,
'BIG5-HKSCS' => true,
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
'EUC-JP' => true, 'EUCJP' => true,
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
];
if (isset($htmlspecialcharsCharsets[$charset])) {
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
}
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
// cache the lowercase variant for future iterations
$htmlspecialcharsCharsets[$charset] = true;
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
}
$string = twig_convert_encoding($string, 'UTF-8', $charset);
$string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
return iconv('UTF-8', $charset, $string);
case 'js':
// escape all non-alphanumeric characters
// into their \x or \uHHHH representations
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
$char = $matches[0];
/*
* A few characters have short escape sequences in JSON and JavaScript.
* Escape sequences supported only by JavaScript, not JSON, are omitted.
* \" is also supported but omitted, because the resulting string is not HTML safe.
*/
static $shortMap = [
'\\' => '\\\\',
'/' => '\\/',
"\x08" => '\b',
"\x0C" => '\f',
"\x0A" => '\n',
"\x0D" => '\r',
"\x09" => '\t',
];
if (isset($shortMap[$char])) {
return $shortMap[$char];
}
$codepoint = mb_ord($char, 'UTF-8');
if (0x10000 > $codepoint) {
return sprintf('\u%04X', $codepoint);
}
// Split characters outside the BMP into surrogate pairs
// https://tools.ietf.org/html/rfc2781.html#section-2.1
$u = $codepoint - 0x10000;
$high = 0xD800 | ($u >> 10);
$low = 0xDC00 | ($u & 0x3FF);
return sprintf('\u%04X\u%04X', $high, $low);
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'css':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
$char = $matches[0];
return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'html_attr':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
/**
* This function is adapted from code coming from Zend Framework.
*
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://framework.zend.com/license/new-bsd New BSD License
*/
$chr = $matches[0];
$ord = \ord($chr);
/*
* The following replaces characters undefined in HTML with the
* hex entity for the Unicode replacement character.
*/
if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
return '&#xFFFD;';
}
/*
* Check if the current character to escape has a name entity we should
* replace it with while grabbing the hex value of the character.
*/
if (1 === \strlen($chr)) {
/*
* While HTML supports far more named entities, the lowest common denominator
* has become HTML5's XML Serialisation which is restricted to the those named
* entities that XML supports. Using HTML entities would result in this error:
* XML Parsing Error: undefined entity
*/
static $entityMap = [
34 => '&quot;', /* quotation mark */
38 => '&amp;', /* ampersand */
60 => '&lt;', /* less-than sign */
62 => '&gt;', /* greater-than sign */
];
if (isset($entityMap[$ord])) {
return $entityMap[$ord];
}
return sprintf('&#x%02X;', $ord);
}
/*
* Per OWASP recommendations, we'll use hex entities for any other
* characters where a named entity does not exist.
*/
return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'url':
return rawurlencode($string);
default:
// check the ones set on CoreExtension for BC (to be removed in 3.0)
$legacyEscapers = $env->getExtension(CoreExtension::class)->getEscapers(false);
if (array_key_exists($strategy, $legacyEscapers)) {
return $legacyEscapers[$strategy]($env, $string, $charset);
}
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
if (array_key_exists($strategy, $escapers)) {
return $escapers[$strategy]($env, $string, $charset);
}
$escapers = array_merge($legacyEscapers, $escapers);
$validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers)));
throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
}
}
/**
* @internal
*/
function twig_escape_filter_is_safe(Node $filterArgs)
{
foreach ($filterArgs as $arg) {
if ($arg instanceof ConstantExpression) {
return [$arg->getAttribute('value')];
}
return [];
}
return ['html'];
}
}

View File

@@ -11,7 +11,6 @@
namespace Twig\Extension;
use Twig\Environment;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
@@ -25,15 +24,6 @@ use Twig\TwigTest;
*/
interface ExtensionInterface
{
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead
*/
public function initRuntime(Environment $environment);
/**
* Returns the token parser instances to add to the existing list.
*
@@ -75,24 +65,6 @@ interface ExtensionInterface
* @return array<array> First array of unary operators, second array of binary operators
*/
public function getOperators();
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead
*/
public function getGlobals();
/**
* Returns the name of the extension.
*
* @return string The extension name
*
* @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
*/
public function getName();
}
class_alias('Twig\Extension\ExtensionInterface', 'Twig_ExtensionInterface');

View File

@@ -21,6 +21,12 @@ namespace Twig\Extension;
*/
interface GlobalsInterface
{
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*/
public function getGlobals();
}
class_alias('Twig\Extension\GlobalsInterface', 'Twig_Extension_GlobalsInterface');

View File

@@ -11,6 +11,8 @@
namespace Twig\Extension;
use Twig\Environment;
/**
* Enables usage of the deprecated Twig\Extension\AbstractExtension::initRuntime() method.
*
@@ -18,9 +20,17 @@ namespace Twig\Extension;
* deprecated initRuntime() method in your extensions.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Twig 2.7, to be removed in 3.0
*/
interface InitRuntimeInterface
{
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*/
public function initRuntime(Environment $environment);
}
class_alias('Twig\Extension\InitRuntimeInterface', 'Twig_Extension_InitRuntimeInterface');

View File

@@ -13,12 +13,9 @@ namespace Twig\Extension;
use Twig\NodeVisitor\OptimizerNodeVisitor;
/**
* @final
*/
class OptimizerExtension extends AbstractExtension
final class OptimizerExtension extends AbstractExtension
{
protected $optimizers;
private $optimizers;
public function __construct($optimizers = -1)
{
@@ -29,11 +26,6 @@ class OptimizerExtension extends AbstractExtension
{
return [new OptimizerNodeVisitor($this->optimizers)];
}
public function getName()
{
return 'optimizer';
}
}
class_alias('Twig\Extension\OptimizerExtension', 'Twig_Extension_Optimizer');

View File

@@ -41,12 +41,7 @@ class ProfilerExtension extends AbstractExtension
public function getNodeVisitors()
{
return [new ProfilerNodeVisitor(\get_class($this))];
}
public function getName()
{
return 'profiler';
return [new ProfilerNodeVisitor(static::class)];
}
}

View File

@@ -12,17 +12,17 @@
namespace Twig\Extension;
use Twig\NodeVisitor\SandboxNodeVisitor;
use Twig\Sandbox\SecurityNotAllowedMethodError;
use Twig\Sandbox\SecurityNotAllowedPropertyError;
use Twig\Sandbox\SecurityPolicyInterface;
use Twig\Source;
use Twig\TokenParser\SandboxTokenParser;
/**
* @final
*/
class SandboxExtension extends AbstractExtension
final class SandboxExtension extends AbstractExtension
{
protected $sandboxedGlobally;
protected $sandboxed;
protected $policy;
private $sandboxedGlobally;
private $sandboxed;
private $policy;
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false)
{
@@ -77,33 +77,49 @@ class SandboxExtension extends AbstractExtension
}
}
public function checkMethodAllowed($obj, $method)
public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null)
{
if ($this->isSandboxed()) {
try {
$this->policy->checkMethodAllowed($obj, $method);
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function checkPropertyAllowed($obj, $method)
public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null)
{
if ($this->isSandboxed()) {
$this->policy->checkPropertyAllowed($obj, $method);
try {
$this->policy->checkPropertyAllowed($obj, $property);
} catch (SecurityNotAllowedPropertyError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function ensureToStringAllowed($obj)
public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null)
{
if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) {
try {
$this->policy->checkMethodAllowed($obj, '__toString');
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
return $obj;
}
public function getName()
{
return 'sandbox';
}
}
class_alias('Twig\Extension\SandboxExtension', 'Twig_Extension_Sandbox');

View File

@@ -13,32 +13,32 @@ namespace Twig\Extension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
/**
* Internal class.
*
* This class is used by \Twig\Environment as a staging area and must not be used directly.
* Used by \Twig\Environment as a staging area.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class StagingExtension extends AbstractExtension
final class StagingExtension extends AbstractExtension
{
protected $functions = [];
protected $filters = [];
protected $visitors = [];
protected $tokenParsers = [];
protected $globals = [];
protected $tests = [];
private $functions = [];
private $filters = [];
private $visitors = [];
private $tokenParsers = [];
private $tests = [];
public function addFunction($name, $function)
public function addFunction(TwigFunction $function)
{
if (isset($this->functions[$name])) {
@trigger_error(sprintf('Overriding function "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED);
if (isset($this->functions[$function->getName()])) {
throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName()));
}
$this->functions[$name] = $function;
$this->functions[$function->getName()] = $function;
}
public function getFunctions()
@@ -46,13 +46,13 @@ class StagingExtension extends AbstractExtension
return $this->functions;
}
public function addFilter($name, $filter)
public function addFilter(TwigFilter $filter)
{
if (isset($this->filters[$name])) {
@trigger_error(sprintf('Overriding filter "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED);
if (isset($this->filters[$filter->getName()])) {
throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName()));
}
$this->filters[$name] = $filter;
$this->filters[$filter->getName()] = $filter;
}
public function getFilters()
@@ -73,7 +73,7 @@ class StagingExtension extends AbstractExtension
public function addTokenParser(TokenParserInterface $parser)
{
if (isset($this->tokenParsers[$parser->getTag()])) {
@trigger_error(sprintf('Overriding tag "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $parser->getTag()), E_USER_DEPRECATED);
throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag()));
}
$this->tokenParsers[$parser->getTag()] = $parser;
@@ -84,34 +84,19 @@ class StagingExtension extends AbstractExtension
return $this->tokenParsers;
}
public function addGlobal($name, $value)
public function addTest(TwigTest $test)
{
$this->globals[$name] = $value;
if (isset($this->tests[$test->getName()])) {
throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName()));
}
public function getGlobals()
{
return $this->globals;
}
public function addTest($name, $test)
{
if (isset($this->tests[$name])) {
@trigger_error(sprintf('Overriding test "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED);
}
$this->tests[$name] = $test;
$this->tests[$test->getName()] = $test;
}
public function getTests()
{
return $this->tests;
}
public function getName()
{
return 'staging';
}
}
class_alias('Twig\Extension\StagingExtension', 'Twig_Extension_Staging');

View File

@@ -12,10 +12,7 @@
namespace Twig\Extension {
use Twig\TwigFunction;
/**
* @final
*/
class StringLoaderExtension extends AbstractExtension
final class StringLoaderExtension extends AbstractExtension
{
public function getFunctions()
{
@@ -23,11 +20,6 @@ class StringLoaderExtension extends AbstractExtension
new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]),
];
}
public function getName()
{
return 'string_loader';
}
}
class_alias('Twig\Extension\StringLoaderExtension', 'Twig_Extension_StringLoader');
@@ -47,7 +39,7 @@ use Twig\TemplateWrapper;
*
* @return TemplateWrapper
*/
function twig_template_from_string(Environment $env, $template, $name = null)
function twig_template_from_string(Environment $env, $template, string $name = null)
{
return $env->createTemplate((string) $template, $name);
}

View File

@@ -0,0 +1,475 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\RuntimeError;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\GlobalsInterface;
use Twig\Extension\InitRuntimeInterface;
use Twig\Extension\StagingExtension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class ExtensionSet
{
private $extensions;
private $initialized = false;
private $runtimeInitialized = false;
private $staging;
private $parsers;
private $visitors;
private $filters;
private $tests;
private $functions;
private $unaryOperators;
private $binaryOperators;
private $globals;
private $functionCallbacks = [];
private $filterCallbacks = [];
private $lastModified = 0;
public function __construct()
{
$this->staging = new StagingExtension();
}
/**
* Initializes the runtime environment.
*
* @deprecated since Twig 2.7
*/
public function initRuntime(Environment $env)
{
if ($this->runtimeInitialized) {
return;
}
$this->runtimeInitialized = true;
foreach ($this->extensions as $extension) {
if ($extension instanceof InitRuntimeInterface) {
$extension->initRuntime($env);
}
}
}
public function hasExtension(string $class): bool
{
$class = ltrim($class, '\\');
if (!isset($this->extensions[$class]) && class_exists($class, false)) {
// For BC/FC with namespaced aliases
$class = (new \ReflectionClass($class))->name;
}
return isset($this->extensions[$class]);
}
public function getExtension(string $class): ExtensionInterface
{
$class = ltrim($class, '\\');
if (!isset($this->extensions[$class]) && class_exists($class, false)) {
// For BC/FC with namespaced aliases
$class = (new \ReflectionClass($class))->name;
}
if (!isset($this->extensions[$class])) {
throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class));
}
return $this->extensions[$class];
}
/**
* @param ExtensionInterface[] $extensions
*/
public function setExtensions(array $extensions)
{
foreach ($extensions as $extension) {
$this->addExtension($extension);
}
}
/**
* @return ExtensionInterface[]
*/
public function getExtensions(): array
{
return $this->extensions;
}
public function getSignature(): string
{
return json_encode(array_keys($this->extensions));
}
public function isInitialized(): bool
{
return $this->initialized || $this->runtimeInitialized;
}
public function getLastModified(): int
{
if (0 !== $this->lastModified) {
return $this->lastModified;
}
foreach ($this->extensions as $extension) {
$r = new \ReflectionObject($extension);
if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) {
$this->lastModified = $extensionTime;
}
}
return $this->lastModified;
}
public function addExtension(ExtensionInterface $extension)
{
$class = \get_class($extension);
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class));
}
if (isset($this->extensions[$class])) {
throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class));
}
$this->extensions[$class] = $extension;
}
public function addFunction(TwigFunction $function)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName()));
}
$this->staging->addFunction($function);
}
/**
* @return TwigFunction[]
*/
public function getFunctions(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->functions;
}
/**
* @return TwigFunction|false
*/
public function getFunction(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->functions[$name])) {
return $this->functions[$name];
}
foreach ($this->functions as $pattern => $function) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$function->setArguments($matches);
return $function;
}
}
foreach ($this->functionCallbacks as $callback) {
if (false !== $function = $callback($name)) {
return $function;
}
}
return false;
}
public function registerUndefinedFunctionCallback(callable $callable)
{
$this->functionCallbacks[] = $callable;
}
public function addFilter(TwigFilter $filter)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName()));
}
$this->staging->addFilter($filter);
}
/**
* @return TwigFilter[]
*/
public function getFilters(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->filters;
}
/**
* @return TwigFilter|false
*/
public function getFilter(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->filters[$name])) {
return $this->filters[$name];
}
foreach ($this->filters as $pattern => $filter) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = $callback($name)) {
return $filter;
}
}
return false;
}
public function registerUndefinedFilterCallback(callable $callable)
{
$this->filterCallbacks[] = $callable;
}
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
if ($this->initialized) {
throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.');
}
$this->staging->addNodeVisitor($visitor);
}
/**
* @return NodeVisitorInterface[]
*/
public function getNodeVisitors(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->visitors;
}
public function addTokenParser(TokenParserInterface $parser)
{
if ($this->initialized) {
throw new \LogicException('Unable to add a token parser as extensions have already been initialized.');
}
$this->staging->addTokenParser($parser);
}
/**
* @return TokenParserInterface[]
*/
public function getTokenParsers(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->parsers;
}
public function getGlobals(): array
{
if (null !== $this->globals) {
return $this->globals;
}
$globals = [];
foreach ($this->extensions as $extension) {
if (!$extension instanceof GlobalsInterface) {
continue;
}
$extGlobals = $extension->getGlobals();
if (!\is_array($extGlobals)) {
throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension)));
}
$globals = array_merge($globals, $extGlobals);
}
if ($this->initialized) {
$this->globals = $globals;
}
return $globals;
}
public function addTest(TwigTest $test)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName()));
}
$this->staging->addTest($test);
}
/**
* @return TwigTest[]
*/
public function getTests(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->tests;
}
/**
* @return TwigTest|false
*/
public function getTest(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->tests[$name])) {
return $this->tests[$name];
}
foreach ($this->tests as $pattern => $test) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$test->setArguments($matches);
return $test;
}
}
}
return false;
}
public function getUnaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->unaryOperators;
}
public function getBinaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->binaryOperators;
}
private function initExtensions()
{
$this->parsers = [];
$this->filters = [];
$this->functions = [];
$this->tests = [];
$this->visitors = [];
$this->unaryOperators = [];
$this->binaryOperators = [];
foreach ($this->extensions as $extension) {
$this->initExtension($extension);
}
$this->initExtension($this->staging);
// Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
$this->initialized = true;
}
private function initExtension(ExtensionInterface $extension)
{
// filters
foreach ($extension->getFilters() as $filter) {
$this->filters[$filter->getName()] = $filter;
}
// functions
foreach ($extension->getFunctions() as $function) {
$this->functions[$function->getName()] = $function;
}
// tests
foreach ($extension->getTests() as $test) {
$this->tests[$test->getName()] = $test;
}
// token parsers
foreach ($extension->getTokenParsers() as $parser) {
if (!$parser instanceof TokenParserInterface) {
throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.');
}
$this->parsers[] = $parser;
}
// node visitors
foreach ($extension->getNodeVisitors() as $visitor) {
$this->visitors[] = $visitor;
}
// operators
if ($operators = $extension->getOperators()) {
if (!\is_array($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators)));
}
if (2 !== \count($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators)));
}
$this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
$this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
}
}
}
class_alias('Twig\ExtensionSet', 'Twig_ExtensionSet');

View File

@@ -41,7 +41,7 @@ class FileExtensionEscapingStrategy
$name = substr($name, 0, -5);
}
$extension = pathinfo($name, PATHINFO_EXTENSION);
$extension = pathinfo($name, \PATHINFO_EXTENSION);
switch ($extension) {
case 'js':

View File

@@ -19,39 +19,36 @@ use Twig\Error\SyntaxError;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Lexer implements \Twig_LexerInterface
class Lexer
{
protected $tokens;
protected $code;
protected $cursor;
protected $lineno;
protected $end;
protected $state;
protected $states;
protected $brackets;
protected $env;
// to be renamed to $name in 2.0 (where it is private)
protected $filename;
protected $options;
protected $regexes;
protected $position;
protected $positions;
protected $currentVarBlockLine;
private $tokens;
private $code;
private $cursor;
private $lineno;
private $end;
private $state;
private $states;
private $brackets;
private $env;
private $source;
private $options;
private $regexes;
private $position;
private $positions;
private $currentVarBlockLine;
const STATE_DATA = 0;
const STATE_BLOCK = 1;
const STATE_VAR = 2;
const STATE_STRING = 3;
const STATE_INTERPOLATION = 4;
public const STATE_DATA = 0;
public const STATE_BLOCK = 1;
public const STATE_VAR = 2;
public const STATE_STRING = 3;
public const STATE_INTERPOLATION = 4;
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A';
const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
const REGEX_DQ_STRING_DELIM = '/"/A';
const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
const PUNCTUATION = '()[]{}?:.,|';
public const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
public const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A';
public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
public const REGEX_DQ_STRING_DELIM = '/"/A';
public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
public const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Environment $env, array $options = [])
{
@@ -100,9 +97,7 @@ class Lexer implements \Twig_LexerInterface
$this->options['whitespace_trim']. // -
'|'.
$this->options['whitespace_line_trim']. // ~
')?\s*'.
'(?:end%s)'. // endraw or endverbatim
'\s*'.
')?\s*endverbatim\s*'.
'(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}
'|'.
@@ -117,7 +112,7 @@ class Lexer implements \Twig_LexerInterface
// #}
'lex_comment' => '{
(?:'.
preg_quote($this->options['whitespace_trim']).preg_quote($this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n?
preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n?
'|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]*
'|'.
@@ -127,9 +122,7 @@ class Lexer implements \Twig_LexerInterface
// verbatim %}
'lex_block_raw' => '{
\s*
(raw|verbatim)
\s*
\s*verbatim\s*
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s*
'|'.
@@ -160,28 +153,10 @@ class Lexer implements \Twig_LexerInterface
];
}
public function tokenize($code, $name = null)
public function tokenize(Source $source)
{
if (!$code instanceof Source) {
@trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED);
$this->source = new Source($code, $name);
} else {
$this->source = $code;
}
if (((int) ini_get('mbstring.func_overload')) & 2) {
@trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED);
}
if (\function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
} else {
$mbEncoding = null;
}
$this->code = str_replace(["\r\n", "\r"], "\n", $this->source->getCode());
$this->filename = $this->source->getName();
$this->source = $source;
$this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode());
$this->cursor = 0;
$this->lineno = 1;
$this->end = \strlen($this->code);
@@ -192,7 +167,7 @@ class Lexer implements \Twig_LexerInterface
$this->position = -1;
// find all token starts in one go
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE);
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE);
$this->positions = $matches;
while ($this->cursor < $this->end) {
@@ -221,25 +196,21 @@ class Lexer implements \Twig_LexerInterface
}
}
$this->pushToken(Token::EOF_TYPE);
$this->pushToken(/* Token::EOF_TYPE */ -1);
if (!empty($this->brackets)) {
list($expect, $lineno) = array_pop($this->brackets);
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
}
if ($mbEncoding) {
mb_internal_encoding($mbEncoding);
}
return new TokenStream($this->tokens, $this->source);
}
protected function lexData()
private function lexData()
{
// if no matches are left we return the rest of the template as simple text token
if ($this->position == \count($this->positions[0]) - 1) {
$this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor));
$this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor));
$this->cursor = $this->end;
return;
@@ -268,7 +239,7 @@ class Lexer implements \Twig_LexerInterface
$text = rtrim($text, " \t\0\x0B");
}
}
$this->pushToken(Token::TEXT_TYPE, $text);
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
$this->moveCursor($textContent.$position[0]);
switch ($this->positions[1][$this->position][0]) {
@@ -280,30 +251,30 @@ class Lexer implements \Twig_LexerInterface
// raw data?
if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lexRawData($match[1]);
$this->lexRawData();
// {% line \d+ %}
} elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lineno = (int) $match[1];
} else {
$this->pushToken(Token::BLOCK_START_TYPE);
$this->pushToken(/* Token::BLOCK_START_TYPE */ 1);
$this->pushState(self::STATE_BLOCK);
$this->currentVarBlockLine = $this->lineno;
}
break;
case $this->options['tag_variable'][0]:
$this->pushToken(Token::VAR_START_TYPE);
$this->pushToken(/* Token::VAR_START_TYPE */ 2);
$this->pushState(self::STATE_VAR);
$this->currentVarBlockLine = $this->lineno;
break;
}
}
protected function lexBlock()
private function lexBlock()
{
if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::BLOCK_END_TYPE);
$this->pushToken(/* Token::BLOCK_END_TYPE */ 3);
$this->moveCursor($match[0]);
$this->popState();
} else {
@@ -311,10 +282,10 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexVar()
private function lexVar()
{
if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::VAR_END_TYPE);
$this->pushToken(/* Token::VAR_END_TYPE */ 4);
$this->moveCursor($match[0]);
$this->popState();
} else {
@@ -322,7 +293,7 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexExpression()
private function lexExpression()
{
// whitespace
if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) {
@@ -340,21 +311,21 @@ class Lexer implements \Twig_LexerInterface
}
// operators
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0]));
$this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0]));
$this->moveCursor($match[0]);
}
// names
elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::NAME_TYPE, $match[0]);
$this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]);
$this->moveCursor($match[0]);
}
// numbers
elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) {
$number = (float) $match[0]; // floats
if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) {
$number = (int) $match[0]; // integers lower than the maximum
}
$this->pushToken(Token::NUMBER_TYPE, $number);
$this->pushToken(/* Token::NUMBER_TYPE */ 6, $number);
$this->moveCursor($match[0]);
}
// punctuation
@@ -375,12 +346,12 @@ class Lexer implements \Twig_LexerInterface
}
}
$this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
$this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]);
++$this->cursor;
}
// strings
elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)));
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1)));
$this->moveCursor($match[0]);
}
// opening double quoted string
@@ -395,14 +366,10 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexRawData($tag)
private function lexRawData()
{
if ('raw' === $tag) {
@trigger_error(sprintf('Twig Tag "raw" is deprecated since version 1.21. Use "verbatim" instead in %s at line %d.', $this->filename, $this->lineno), E_USER_DEPRECATED);
}
if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source);
if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source);
}
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
@@ -420,27 +387,27 @@ class Lexer implements \Twig_LexerInterface
}
}
$this->pushToken(Token::TEXT_TYPE, $text);
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
}
protected function lexComment()
private function lexComment()
{
if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source);
}
$this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);
}
protected function lexString()
private function lexString()
{
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {
$this->brackets[] = [$this->options['interpolation'][0], $this->lineno];
$this->pushToken(Token::INTERPOLATION_START_TYPE);
$this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10);
$this->moveCursor($match[0]);
$this->pushState(self::STATE_INTERPOLATION);
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) {
$this->pushToken(Token::STRING_TYPE, stripcslashes($match[0]));
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0]));
$this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets);
@@ -456,12 +423,12 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexInterpolation()
private function lexInterpolation()
{
$bracket = end($this->brackets);
if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) {
array_pop($this->brackets);
$this->pushToken(Token::INTERPOLATION_END_TYPE);
$this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11);
$this->moveCursor($match[0]);
$this->popState();
} else {
@@ -469,23 +436,23 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function pushToken($type, $value = '')
private function pushToken($type, $value = '')
{
// do not push empty text tokens
if (Token::TEXT_TYPE === $type && '' === $value) {
if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) {
return;
}
$this->tokens[] = new Token($type, $value, $this->lineno);
}
protected function moveCursor($text)
private function moveCursor($text)
{
$this->cursor += \strlen($text);
$this->lineno += substr_count($text, "\n");
}
protected function getOperatorRegex()
private function getOperatorRegex()
{
$operators = array_merge(
['='],
@@ -499,11 +466,15 @@ class Lexer implements \Twig_LexerInterface
$regex = [];
foreach ($operators as $operator => $length) {
// an operator that ends with a character must be followed by
// a whitespace or a parenthesis
if (ctype_alpha($operator[$length - 1])) {
$r = preg_quote($operator, '/').'(?=[\s()])';
} else {
// a whitespace, a parenthesis, an opening map [ or sequence {
$r = preg_quote($operator, '/');
if (ctype_alpha($operator[$length - 1])) {
$r .= '(?=[\s()\[{])';
}
// an operator that begins with a character must not have a dot or pipe before
if (ctype_alpha($operator[0])) {
$r = '(?<![\.\|])'.$r;
}
// an operator with a space can be any amount of whitespaces
@@ -515,13 +486,13 @@ class Lexer implements \Twig_LexerInterface
return '/'.implode('|', $regex).'/A';
}
protected function pushState($state)
private function pushState($state)
{
$this->states[] = $this->state;
$this->state = $state;
}
protected function popState()
private function popState()
{
if (0 === \count($this->states)) {
throw new \LogicException('Cannot pop state without a previous state.');

View File

@@ -24,13 +24,11 @@ use Twig\Source;
*
* This loader should only be used for unit testing.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
final class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{
protected $templates = [];
private $templates = [];
/**
* @param array $templates An array of templates (keys are the names, and values are the source code)
@@ -48,19 +46,7 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
*/
public function setTemplate($name, $template)
{
$this->templates[(string) $name] = $template;
}
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
return $this->templates[$name];
$this->templates[$name] = $template;
}
public function getSourceContext($name)
@@ -75,12 +61,11 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function exists($name)
{
return isset($this->templates[(string) $name]);
return isset($this->templates[$name]);
}
public function getCacheKey($name)
{
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
@@ -90,7 +75,6 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function isFresh($name, $time)
{
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}

View File

@@ -12,19 +12,16 @@
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Loads templates from other loaders.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
final class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{
private $hasSourceCache = [];
protected $loaders = [];
private $loaders = [];
/**
* @param LoaderInterface[] $loaders
@@ -50,40 +47,16 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
return $this->loaders;
}
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
continue;
}
try {
return $loader->getSource($name);
} catch (LoaderError $e) {
$exceptions[] = $e->getMessage();
}
}
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
}
public function getSourceContext($name)
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
if (!$loader->exists($name)) {
continue;
}
try {
if ($loader instanceof SourceContextLoaderInterface) {
return $loader->getSourceContext($name);
}
return new Source($loader->getSource($name), $name);
} catch (LoaderError $e) {
$exceptions[] = $e->getMessage();
}
@@ -94,31 +67,14 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function exists($name)
{
$name = (string) $name;
if (isset($this->hasSourceCache[$name])) {
return $this->hasSourceCache[$name];
}
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface) {
if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = true;
}
continue;
}
try {
if ($loader instanceof SourceContextLoaderInterface) {
$loader->getSourceContext($name);
} else {
$loader->getSource($name);
}
return $this->hasSourceCache[$name] = true;
} catch (LoaderError $e) {
}
}
return $this->hasSourceCache[$name] = false;
@@ -128,7 +84,7 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
if (!$loader->exists($name)) {
continue;
}
@@ -146,7 +102,7 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
if (!$loader->exists($name)) {
continue;
}

View File

@@ -12,22 +12,12 @@
namespace Twig\Loader;
/**
* Adds an exists() method for loaders.
* Empty interface for Twig 1.x compatibility.
*
* @author Florin Patan <florinpatan@gmail.com>
*
* @deprecated since 1.12 (to be removed in 3.0)
* @deprecated since Twig 2.7, to be removed in 3.0
*/
interface ExistsLoaderInterface
interface ExistsLoaderInterface extends LoaderInterface
{
/**
* Check if we have the source code of a template, given its name.
*
* @param string $name The name of the template to check if we can load
*
* @return bool If the template source code is handled by this loader or not
*/
public function exists($name);
}
class_alias('Twig\Loader\ExistsLoaderInterface', 'Twig_ExistsLoaderInterface');

View File

@@ -22,7 +22,7 @@ use Twig\Source;
class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{
/** Identifier of the main namespace. */
const MAIN_NAMESPACE = '__main__';
public const MAIN_NAMESPACE = '__main__';
protected $paths = [];
protected $cache = [];
@@ -34,10 +34,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
* @param string|array $paths A path or an array of paths where to look for templates
* @param string|null $rootPath The root path common to all relative paths (null for getcwd())
*/
public function __construct($paths = [], $rootPath = null)
public function __construct($paths = [], string $rootPath = null)
{
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
if (false !== $realPath = realpath($rootPath)) {
if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {
$this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
}
@@ -136,17 +136,6 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
}
}
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
if (null === ($path = $this->findTemplate($name)) || false === $path) {
return '';
}
return file_get_contents($path);
}
public function getSourceContext($name)
{
if (null === ($path = $this->findTemplate($name)) || false === $path) {
@@ -177,13 +166,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
return true;
}
try {
return null !== ($path = $this->findTemplate($name, false)) && false !== $path;
} catch (LoaderError $e) {
@trigger_error(sprintf('In %s::findTemplate(), you must accept a second argument that when set to "false" returns "false" instead of throwing an exception. Not supporting this argument is deprecated since version 1.27.', \get_class($this)), E_USER_DEPRECATED);
return false;
}
}
public function isFresh($name, $time)
@@ -199,13 +182,15 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
/**
* Checks if the template can be found.
*
* In Twig 3.0, findTemplate must return a string or null (returning false won't work anymore).
*
* @param string $name The template name
* @param bool $throw Whether to throw an exception when an error occurs
*
* @return string|false|null The template name or false/null
*/
protected function findTemplate($name)
protected function findTemplate($name, $throw = true)
{
$throw = \func_num_args() > 1 ? func_get_arg(1) : true;
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
@@ -221,9 +206,9 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
}
try {
$this->validateName($name);
list($namespace, $shortname) = $this->parseName($name);
$this->validateName($shortname);
} catch (LoaderError $e) {
if (!$throw) {
return false;
@@ -265,7 +250,12 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
throw new LoaderError($this->errorCache[$name]);
}
protected function parseName($name, $default = self::MAIN_NAMESPACE)
private function normalizeName($name)
{
return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
}
private function parseName($name, $default = self::MAIN_NAMESPACE)
{
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
@@ -281,12 +271,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
return [$default, $name];
}
protected function normalizeName($name)
{
return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
}
protected function validateName($name)
private function validateName($name)
{
if (false !== strpos($name, "\0")) {
throw new LoaderError('A template name cannot contain NUL bytes.');
@@ -312,10 +297,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
{
return strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === substr($file, 1, 1)
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
|| null !== parse_url($file, \PHP_URL_SCHEME)
;
}
}

View File

@@ -12,6 +12,7 @@
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Interface all loaders must implement.
@@ -21,17 +22,15 @@ use Twig\Error\LoaderError;
interface LoaderInterface
{
/**
* Gets the source code of a template, given its name.
* Returns the source context for a given template logical name.
*
* @param string $name The name of the template to load
* @param string $name The template logical name
*
* @return string The template source code
* @return Source
*
* @throws LoaderError When $name is not found
*
* @deprecated since 1.27 (to be removed in 2.0), implement Twig\Loader\SourceContextLoaderInterface
*/
public function getSource($name);
public function getSourceContext($name);
/**
* Gets the cache key to use for the cache for a given template name.
@@ -56,6 +55,15 @@ interface LoaderInterface
* @throws LoaderError When $name is not found
*/
public function isFresh($name, $time);
/**
* Check if we have the source code of a template, given its name.
*
* @param string $name The name of the template to check if we can load
*
* @return bool If the template source code is handled by this loader or not
*/
public function exists($name);
}
class_alias('Twig\Loader\LoaderInterface', 'Twig_LoaderInterface');

View File

@@ -11,28 +11,11 @@
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Adds a getSourceContext() method for loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since 1.27 (to be removed in 3.0)
* Empty interface for Twig 1.x compatibility.
*/
interface SourceContextLoaderInterface
interface SourceContextLoaderInterface extends LoaderInterface
{
/**
* Returns the source context for a given template logical name.
*
* @param string $name The template logical name
*
* @return Source
*
* @throws LoaderError When $name is not found
*/
public function getSourceContext($name);
}
class_alias('Twig\Loader\SourceContextLoaderInterface', 'Twig_SourceContextLoaderInterface');

View File

@@ -16,10 +16,10 @@ namespace Twig;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Markup implements \Countable
class Markup implements \Countable, \JsonSerializable
{
protected $content;
protected $charset;
private $content;
private $charset;
public function __construct($content, $charset)
{
@@ -32,9 +32,22 @@ class Markup implements \Countable
return $this->content;
}
/**
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return \function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : \strlen($this->content);
return mb_strlen($this->content, $this->charset);
}
/**
* @return mixed
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->content;
}
}

View File

@@ -26,7 +26,7 @@ use Twig\Compiler;
*/
class AutoEscapeNode extends Node
{
public function __construct($value, \Twig_NodeInterface $body, $lineno, $tag = 'autoescape')
public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape')
{
parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag);
}

View File

@@ -21,7 +21,7 @@ use Twig\Compiler;
*/
class BlockNode extends Node
{
public function __construct($name, \Twig_NodeInterface $body, $lineno, $tag = null)
public function __construct(string $name, Node $body, int $lineno, string $tag = null)
{
parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag);
}
@@ -32,6 +32,7 @@ class BlockNode extends Node
->addDebugInfo($this)
->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n")
->indent()
->write("\$macros = \$this->macros;\n")
;
$compiler

View File

@@ -21,7 +21,7 @@ use Twig\Compiler;
*/
class BlockReferenceNode extends Node implements NodeOutputInterface
{
public function __construct($name, $lineno, $tag = null)
public function __construct(string $name, int $lineno, string $tag = null)
{
parent::__construct([], ['name' => $name], $lineno, $tag);
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class CheckSecurityCallNode extends Node
{
public function compile(Compiler $compiler)
{
$compiler
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
->write("\$this->checkSecurity();\n")
;
}
}

View File

@@ -18,9 +18,9 @@ use Twig\Compiler;
*/
class CheckSecurityNode extends Node
{
protected $usedFilters;
protected $usedTags;
protected $usedFunctions;
private $usedFilters;
private $usedTags;
private $usedFunctions;
public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)
{
@@ -45,10 +45,13 @@ class CheckSecurityNode extends Node
}
$compiler
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
->write('$tags = ')->repr(array_filter($tags))->raw(";\n")
->write('$filters = ')->repr(array_filter($filters))->raw(";\n")
->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n")
->write("\n")
->write("public function checkSecurity()\n")
->write("{\n")
->indent()
->write('static $tags = ')->repr(array_filter($tags))->raw(";\n")
->write('static $filters = ')->repr(array_filter($filters))->raw(";\n")
->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n")
->write("try {\n")
->indent()
->write("\$this->sandbox->checkSecurity(\n")
@@ -61,7 +64,7 @@ class CheckSecurityNode extends Node
->outdent()
->write("} catch (SecurityError \$e) {\n")
->indent()
->write("\$e->setSourceContext(\$this->getSourceContext());\n\n")
->write("\$e->setSourceContext(\$this->source);\n\n")
->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n")
@@ -78,6 +81,8 @@ class CheckSecurityNode extends Node
->write("throw \$e;\n")
->outdent()
->write("}\n\n")
->outdent()
->write("}\n")
;
}
}

View File

@@ -33,10 +33,13 @@ class CheckToStringNode extends AbstractExpression
public function compile(Compiler $compiler)
{
$expr = $this->getNode('expr');
$compiler
->raw('$this->sandbox->ensureToStringAllowed(')
->subcompile($this->getNode('expr'))
->raw(')')
->subcompile($expr)
->raw(', ')
->repr($expr->getTemplateLine())
->raw(', $this->source)')
;
}
}

View File

@@ -22,7 +22,7 @@ use Twig\Node\Expression\ConstantExpression;
*/
class DeprecatedNode extends Node
{
public function __construct(AbstractExpression $expr, $lineno, $tag = null)
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}

View File

@@ -21,7 +21,7 @@ use Twig\Node\Expression\AbstractExpression;
*/
class DoNode extends Node
{
public function __construct(AbstractExpression $expr, $lineno, $tag = null)
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}

View File

@@ -23,13 +23,11 @@ use Twig\Node\Expression\ConstantExpression;
class EmbedNode extends IncludeNode
{
// we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
public function __construct($name, $index, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
{
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('name', $name);
// to be removed in 2.0, used name instead
$this->setAttribute('filename', $name);
$this->setAttribute('index', $index);
}

View File

@@ -15,9 +15,9 @@ use Twig\Compiler;
class ArrayExpression extends AbstractExpression
{
protected $index;
private $index;
public function __construct(array $elements, $lineno)
public function __construct(array $elements, int $lineno)
{
parent::__construct($elements, [], $lineno);

View File

@@ -44,7 +44,7 @@ class ArrowFunctionExpression extends AbstractExpression
;
}
$compiler
->raw(') use ($context) { ')
->raw(') use ($context, $macros) { ')
;
foreach ($this->getNode('names') as $name) {
$compiler

View File

@@ -14,10 +14,11 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
abstract class AbstractBinary extends AbstractExpression
{
public function __construct(\Twig_NodeInterface $left, \Twig_NodeInterface $right, $lineno)
public function __construct(Node $left, Node $right, int $lineno)
{
parent::__construct(['left' => $left, 'right' => $right], [], $lineno);
}

View File

@@ -15,21 +15,6 @@ use Twig\Compiler;
class PowerBinary extends AbstractBinary
{
public function compile(Compiler $compiler)
{
if (\PHP_VERSION_ID >= 50600) {
return parent::compile($compiler);
}
$compiler
->raw('pow(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
public function operator(Compiler $compiler)
{
return $compiler->raw('**');

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class SpaceshipBinary extends AbstractBinary
{
public function operator(Compiler $compiler)
{
return $compiler->raw('<=>');
}
}

View File

@@ -22,17 +22,8 @@ use Twig\Node\Node;
*/
class BlockReferenceExpression extends AbstractExpression
{
/**
* @param Node|null $template
*/
public function __construct(\Twig_NodeInterface $name, $template = null, $lineno, $tag = null)
public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null)
{
if (\is_bool($template)) {
@trigger_error(sprintf('The %s method "$asString" argument is deprecated since version 1.28 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED);
$template = null;
}
$nodes = ['name' => $name];
if (null !== $template) {
$nodes['template'] = $template;
@@ -58,7 +49,7 @@ class BlockReferenceExpression extends AbstractExpression
}
}
private function compileTemplateCall(Compiler $compiler, $method)
private function compileTemplateCall(Compiler $compiler, string $method): Compiler
{
if (!$this->hasNode('template')) {
$compiler->write('$this');
@@ -75,12 +66,11 @@ class BlockReferenceExpression extends AbstractExpression
}
$compiler->raw(sprintf('->%s', $method));
$this->compileBlockArguments($compiler);
return $compiler;
return $this->compileBlockArguments($compiler);
}
private function compileBlockArguments(Compiler $compiler)
private function compileBlockArguments(Compiler $compiler): Compiler
{
$compiler
->raw('(')

View File

@@ -22,39 +22,39 @@ abstract class CallExpression extends AbstractExpression
protected function compileCallable(Compiler $compiler)
{
$closingParenthesis = false;
$isArray = false;
if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) {
$callable = $this->getAttribute('callable');
if (\is_string($callable) && false === strpos($callable, '::')) {
$compiler->raw($callable);
} else {
list($r, $callable) = $this->reflectCallable($callable);
if ($r instanceof \ReflectionMethod && \is_string($callable[0])) {
if ($r->isStatic()) {
[$r, $callable] = $this->reflectCallable($callable);
if (\is_string($callable)) {
$compiler->raw($callable);
} elseif (\is_array($callable) && \is_string($callable[0])) {
if (!$r instanceof \ReflectionMethod || $r->isStatic()) {
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
} else {
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
}
} elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) {
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($callable[0]), $callable[1]));
} elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) {
$class = \get_class($callable[0]);
if (!$compiler->getEnvironment()->hasExtension($class)) {
// Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class));
} else {
$type = ucfirst($this->getAttribute('type'));
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', $type, $this->getAttribute('name')));
$closingParenthesis = true;
$isArray = true;
}
}
} else {
$compiler->raw($this->getAttribute('thing')->compile());
$compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\')));
}
$this->compileArguments($compiler, $isArray);
if ($closingParenthesis) {
$compiler->raw(')');
$compiler->raw(sprintf('->%s', $callable[1]));
} else {
$compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
}
}
$this->compileArguments($compiler);
}
protected function compileArguments(Compiler $compiler, $isArray = false)
{
$compiler->raw($isArray ? '[' : '(');
@@ -93,10 +93,8 @@ abstract class CallExpression extends AbstractExpression
}
if ($this->hasNode('arguments')) {
$callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null;
$callable = $this->getAttribute('callable');
$arguments = $this->getArguments($callable, $this->getNode('arguments'));
foreach ($arguments as $node) {
if (!$first) {
$compiler->raw(', ');
@@ -142,14 +140,23 @@ abstract class CallExpression extends AbstractExpression
throw new \LogicException($message);
}
$callableParameters = $this->getCallableParameters($callable, $isVariadic);
list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic);
$arguments = [];
$names = [];
$missingArguments = [];
$optionalArguments = [];
$pos = 0;
foreach ($callableParameters as $callableParameter) {
$names[] = $name = $this->normalizeName($callableParameter->name);
$name = $this->normalizeName($callableParameter->name);
if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
if ('start' === $name) {
$name = 'low';
} elseif ('end' === $name) {
$name = 'high';
}
}
$names[] = $name;
if (\array_key_exists($name, $parameters)) {
if (\array_key_exists($pos, $parameters)) {
@@ -187,7 +194,7 @@ abstract class CallExpression extends AbstractExpression
}
if ($isVariadic) {
$arbitraryArguments = new ArrayExpression([], -1);
$arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);
foreach ($parameters as $key => $value) {
if (\is_int($key)) {
$arbitraryArguments->addElement($value);
@@ -230,12 +237,9 @@ abstract class CallExpression extends AbstractExpression
return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name));
}
private function getCallableParameters($callable, $isVariadic)
private function getCallableParameters($callable, bool $isVariadic): array
{
list($r) = $this->reflectCallable($callable);
if (null === $r) {
return [];
}
[$r, , $callableName] = $this->reflectCallable($callable);
$parameters = $r->getParameters();
if ($this->hasNode('node')) {
@@ -252,21 +256,21 @@ abstract class CallExpression extends AbstractExpression
array_shift($parameters);
}
}
$isPhpVariadic = false;
if ($isVariadic) {
$argument = end($parameters);
if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
$isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName();
if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
array_pop($parameters);
} elseif ($argument && $argument->isVariadic()) {
array_pop($parameters);
$isPhpVariadic = true;
} else {
$callableName = $r->name;
if ($r instanceof \ReflectionMethod) {
$callableName = $r->getDeclaringClass()->name.'::'.$callableName;
}
throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name')));
}
}
return $parameters;
return [$parameters, $isPhpVariadic];
}
private function reflectCallable($callable)
@@ -275,30 +279,44 @@ abstract class CallExpression extends AbstractExpression
return $this->reflector;
}
if (\is_array($callable)) {
if (!method_exists($callable[0], $callable[1])) {
// __call()
return [null, []];
}
$r = new \ReflectionMethod($callable[0], $callable[1]);
} elseif (\is_object($callable) && !$callable instanceof \Closure) {
$r = new \ReflectionObject($callable);
$r = $r->getMethod('__invoke');
$callable = [$callable, '__invoke'];
} elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$class = substr($callable, 0, $pos);
$method = substr($callable, $pos + 2);
if (!method_exists($class, $method)) {
// __staticCall()
return [null, []];
}
$r = new \ReflectionMethod($callable);
$callable = [$class, $method];
} else {
$r = new \ReflectionFunction($callable);
if (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];
}
return $this->reflector = [$r, $callable];
if (\is_array($callable) && method_exists($callable[0], $callable[1])) {
$r = new \ReflectionMethod($callable[0], $callable[1]);
return $this->reflector = [$r, $callable, $r->class.'::'.$r->name];
}
$checkVisibility = $callable instanceof \Closure;
try {
$closure = \Closure::fromCallable($callable);
} catch (\TypeError $e) {
throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e);
}
$r = new \ReflectionFunction($closure);
if (false !== strpos($r->name, '{closure}')) {
return $this->reflector = [$r, $callable, 'Closure'];
}
if ($object = $r->getClosureThis()) {
$callable = [$object, $r->name];
$callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name;
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
$callableName = $class->name.'::'.$r->name;
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
$callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
} else {
$callable = $callableName = $r->name;
}
if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) {
$callable = $r->getClosure();
}
return $this->reflector = [$r, $callable, $callableName];
}
}

View File

@@ -16,7 +16,7 @@ use Twig\Compiler;
class ConditionalExpression extends AbstractExpression
{
public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, $lineno)
public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno)
{
parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno);
}

View File

@@ -16,7 +16,7 @@ use Twig\Compiler;
class ConstantExpression extends AbstractExpression
{
public function __construct($value, $lineno)
public function __construct($value, int $lineno)
{
parent::__construct([], ['value' => $value], $lineno);
}

View File

@@ -29,7 +29,7 @@ use Twig\Node\Node;
*/
class DefaultFilter extends FilterExpression
{
public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null)
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{
$default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine());

View File

@@ -13,11 +13,11 @@
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\TwigFilter;
use Twig\Node\Node;
class FilterExpression extends CallExpression
{
public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null)
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{
parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag);
}
@@ -29,16 +29,11 @@ class FilterExpression extends CallExpression
$this->setAttribute('name', $name);
$this->setAttribute('type', 'filter');
$this->setAttribute('thing', $filter);
$this->setAttribute('needs_environment', $filter->needsEnvironment());
$this->setAttribute('needs_context', $filter->needsContext());
$this->setAttribute('arguments', $filter->getArguments());
if ($filter instanceof \Twig_FilterCallableInterface || $filter instanceof TwigFilter) {
$this->setAttribute('callable', $filter->getCallable());
}
if ($filter instanceof TwigFilter) {
$this->setAttribute('is_variadic', $filter->isVariadic());
}
$this->compileCallable($compiler);
}

Some files were not shown because too many files have changed in this diff Show More