Compare commits

...

167 Commits

Author SHA1 Message Date
Pierre Goiffon
2fd9523c16 🔖 Prepare 2.7.10 version 2024-01-05 15:50:41 +01:00
Pierre Goiffon
a4f6f6e877 N°4368 Fix CORB blocking regression (#598)
Don't send X-Content-Type-Options HTTP header for certain WebPage impl to workaround CORB blocking
To disable globally this new behavior introduced in 9865bf07, set the `security.enable_header_xcontent_type_options` config parameter to false

Thanks @Molkobain for the review !
2024-01-05 10:41:18 +01:00
Molkobain
94c604a6af N°3062 - Fix setup.css compilation test to ensure that it is versioned correctly. 2023-12-21 12:00:26 +01:00
Pierre Goiffon
6995a3c641 N°6889 backup mysqldump call : restore possibility to connect using socket protocol (#591)
With previous fix (N°6123) we forced to use the tcp protocol each time. This was blocking for users wanting to connect using the socket protocol on localhost.

Now for localhost we will : 
- send both port and protocol arguments if the `db_host` config parameter does contain a port
- don't send any of the port or protocol arguments if `db_host` doesn't contain a port
2023-12-20 15:19:50 +01:00
Pierre Goiffon
9865bf0779 N°4368 add sending X-Content-Type-Options HTTP header
Replace in consumers the \WebPage::add_xframe_options call by \WebPage::add_http_headers
2023-12-19 18:25:26 +01:00
Pierre Goiffon
d5449cca42 💡 iTopMutex: add link to mysql doc 2023-12-08 17:20:37 +01:00
Molkobain
5d38d22c50 N°7023 - Fix regression from the initial fix that throw exceptions even for ext. keys set programatically (eg. ComputeValues), which we still want to allow 2023-12-06 16:27:37 +01:00
Pierre Goiffon
99d69493d1 N°7023 - Update tests so that we are now checking negative ext. keys 2023-12-04 22:36:26 +01:00
Molkobain
c9bb628c30 N°7023 - Improve debug message on portal \DBObject::CheckChangedExtKeysValues() call 2023-12-04 22:36:09 +01:00
Molkobain
08e8d15d78 N°7023 - Fix check to write error when adding a contact on a new user request on the end-users portal 2023-12-04 22:35:07 +01:00
Molkobain
7b59df216b N°7005 - Fix portal stylesheets not being re-compiled when outdated
Stylesheets should remain as a relative path in the portal configuration, only when consumed by the TWIG should they become URLs

Note that if not absolute, URLs will be append to ITOP/pages/
2023-11-24 16:40:58 +01:00
Molkobain
cb5eab812e N°938 - Update compiled portal stylesheet 2023-11-24 16:22:36 +01:00
Anne-Catherine
c9b73a7fe2 N°938 - Improve print of portal object page and portal dashboard page (#537)
N°938 - Improve print of portal object page and portal dashboard page
2023-11-24 15:17:42 +01:00
Molkobain
3b2da39469 N°6989 - Security hardening 2023-11-22 18:02:50 +01:00
odain
fc22d91232 N°6949 - Run subset of itop core unit tests after the setup when validating a module github code 2023-11-22 10:14:58 +01:00
Stephen Abello
b10bcb976d N°6951 - Security hardening 2023-11-21 09:42:11 +01:00
Pierre Goiffon
5a43448644 N°6458 Security hardening 2023-11-15 11:14:07 +01:00
Pierre Goiffon
77409eed99 🎨 DBObject small phpdoc fixes 2023-11-13 16:25:37 +01:00
Anne-Catherine
83a70daf68 N°6887 - Fix excessive OQL requests to display user's grant matrix (#564)
* N°6887 - Fix excessive OQL requests to display user's grant matrix

* N°6887 - Rename variable and add PHPDoc

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-11-08 14:57:28 +01:00
Molkobain
a49a4e6c2b N°6886 - Add OAuth tests folder to removable directories list 2023-10-30 16:54:28 +01:00
Romain Quetiez
7419749ba6 Prerequisites for boosting tests 2023-10-26 20:51:28 +02:00
Romain Quetiez
037dfe1df6 Optimize tests execution time 2023-10-25 17:51:12 +02:00
Romain Quetiez
0b26d45014 Prerequisites for boosting tests 2023-10-25 17:50:41 +02:00
Molkobain
4fd8177165 N°3715 - Fix unit tests 2023-10-23 14:55:06 +02:00
Anne-Catherine
a2cdf214f0 N°3715 - Export above 1000 entries ignore obsolete data from user preference (#468)
* N°3715 - Export above 10000 entries ignore obsolete data from user preference
2023-10-20 17:02:16 +02:00
Anne-Catherine
013173019f N°2909 - Search on Enum, Date, TagSet,... with index fails (#496) 2023-10-20 16:45:35 +02:00
Stephen Abello
9469681a0c N°6777 - Security hardening 2023-10-17 09:12:40 +02:00
Pierre Goiffon
c72cb7e70e N°6606 security hardening 2023-10-13 17:15:37 +02:00
Pierre Goiffon
9df92665e0 N°6606 Backport of utils::ENUM_SANITIZATION_FILTER_* constants
Were introduced in 3.0.0, but not added to the support/2.7 branch
2023-10-13 17:10:35 +02:00
Molkobain
8ecebee511 PHP unit tests: Fix typo for "final private" methods as they can't be both 2023-09-20 16:11:39 +02:00
Pierre Goiffon
35cd965360 N°6629 Update ci_description php_version 2023-09-19 12:25:40 +02:00
Pierre Goiffon
e5dd51f637 N°6600 Portal download attachment : don't display anymore SQL query on attachment not found error (#525) 2023-09-19 09:54:43 +02:00
Molkobain
0a6c82dfe1 N°6752 - PHP unit tests: Fix typo in postbuild_integration.xml.dist 2023-09-19 08:37:46 +02:00
Molkobain
24c0f4950f Add missing .htaccess / web.config files in .gitignore for /extensions folder 2023-09-18 15:26:06 +02:00
Molkobain
d4dbbc59d4 N°6754 - PHP unit tests: Add local PHPUnit XML files to .gitignore 2023-09-18 15:23:52 +02:00
Molkobain
dc0cd44c79 N°6752 - PHP unit tests: Migrate usages of unitestautoload.php to composer autoloader 2023-09-18 15:14:44 +02:00
odain
6c6131ce03 N°5491 - test enhancement to reduce false positive 2023-09-15 10:07:42 +02:00
odain-cbd
e76728b2bf N°5491 - Inconsistent dictionnary entries regarding arguments to pass to Dict::Format-test first (#545) 2023-09-13 12:02:49 +02:00
Pierre Goiffon
e946fc65fc N°6709 New ItopTestCase::RequireOnceCurrentModuleFile 2023-09-07 14:38:19 +02:00
Pierre Goiffon
0d8ff7bbac N°6629 Set commit tests back to Mysql
For now we have perf issues on Jenkins with MariaDB (see N°6694)
2023-09-04 10:25:41 +02:00
Anne-Catherine
bf768311c2 N°5136 - "Select All objects" add obsolete objects even if the parameter show obsolete data is not activated (#467) 2023-08-31 15:13:20 +02:00
Molkobain
a8c689c6c0 N°6436 - Add unit test to ensure that we don't lose an API during merge between branches 2023-08-18 09:55:45 +02:00
Molkobain
1990ccb5d8 N°6436 - Move interfaces enumeration from 1 line to 1 line / interface (and re-ordered them) for easier merges in newer branches 2023-08-18 09:52:55 +02:00
Molkobain
e107be56e4 N°6097 - Tests: Fix missing hook entry in PHPUnit XML file that led to compiled environment being re-build for each test case 2023-08-18 09:51:15 +02:00
Molkobain
ed6df77cbb N°6097 - Tests: Optimize performances by creating custom env. only once and re-using it across test classes 2023-08-10 15:45:39 +02:00
Molkobain
1ad28312ec N°6097 - Tests: Introduce autoloader for "utility" classes and move them to a sub-folder for better organization as folder was still messy
Note that unittestautoload.php is now useless. We just keep for now until everything is migrated (projects / branches / modules)
2023-08-10 15:45:39 +02:00
Molkobain
f002aa04cd N°6097 - Tests: Enable PHP unit tests on a custom DataModel 2023-08-10 15:45:39 +02:00
Molkobain
b86d70623e N°6097 - Tests: Temporarily add test case for the new ItopCustomDatamodelTestCase class 2023-08-10 15:45:39 +02:00
Molkobain
fe3467309d N°6097 - Tests: Refactor base test classes for better extensibility 2023-08-10 15:45:39 +02:00
Molkobain
851ab9c356 N°6097 - Add \utils::GetDataPath() method to avoid duplicating manual path build 2023-08-10 15:45:39 +02:00
Molkobain
aef3c2e609 N°6097 - Fix \CMDBSource::DropDB() not resetting cache like \CMDBSource::DropTable() which can lead to errors when trying to re-create it afterwards 2023-08-10 15:45:39 +02:00
Pierre Goiffon
6d13397ba1 Add other integration tests in the beforeSetup group
All of those tests can be ran without a running iTop instance, and are blocking
2023-08-08 15:33:09 +02:00
Pierre Goiffon
c0c8a13864 💡 \MetaModel::GetObject : remove documented throw Exception 2023-08-04 14:55:38 +02:00
Pierre Goiffon
cd9beec313 Merge remote-tracking branch 'origin/support/2.6' into support/2.7 2023-07-26 12:07:09 +02:00
Pierre Goiffon
8295eaed90 Merge remote-tracking branch 'origin/support/2.5' into support/2.6 2023-07-26 12:06:32 +02:00
Eric Espie
5475b9fbbe N°3454 - MoveToProd in 2 steps - fix utils::GetCurrentModuleName() 2023-07-25 17:44:43 +02:00
Eric Espie
6f8e7c7002 N°3454 - MoveToProd in 2 steps - fix utils::GetCurrentModuleUrl() 2023-07-25 17:20:37 +02:00
Pierre Goiffon
bc7c1b4744 N°6590 Fix DictionariesConsistencyTest for PL dict files 2023-07-24 11:14:37 +02:00
Eric Espie
4d8246c4d8 N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 (changed config variable name) 2023-07-19 15:13:43 +02:00
Eric Espie
5c61d725e1 N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 (changed config variable name) 2023-07-19 15:06:00 +02:00
Eric Espie
2c4cad4dac N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 (avoid unnecessary calls) 2023-07-19 10:37:41 +02:00
Eric Espie
da45651121 Merge branch 'feature/6548_Hide_DBHost_and_DBUser_in_log' into support/2.7 2023-07-18 09:34:48 +02:00
Eric Espie
d388ce9a06 Merge branch 'feature/6548_Hide_DBHost_and_DBUser_in_log' into support/2.7 2023-07-18 09:17:40 +02:00
Eric Espie
47e71d8838 Merge branch 'feature/6436-Integrate_Performance_Audit_extensibility' into support/2.7 2023-07-18 09:17:05 +02:00
Stephen Abello
2b5973ec67 N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 2023-07-18 09:15:37 +02:00
Eric Espie
78396d8e4a 6548 - [ER] Hide DBHost and DBUser in log 2023-07-10 17:37:27 +02:00
Stephen Abello
9afc22bd8f N°6123 - Add tests and comments 2023-07-07 09:29:15 +02:00
Pierre Goiffon
264a8cd70a N°6494 - Some tests are run twice, some never
(cherry picked from commit a2a0b2cd0b)

(cherry picked from commit 4c9ea0c9d4)

# Conflicts:
#	tests/php-unit-tests/integration-tests/DictionariesConsistencyTest.php
2023-07-06 15:45:09 +02:00
Stephen Abello
aa1834170b N°6427 - Fix SwiftMailer not retrieving sendmail path 2023-07-06 14:31:54 +02:00
Stephen Abello
f94d67ab35 N°6340 - Fix permission refused when sending an email and renewing OAuth token in synchronous mode 2023-07-06 10:28:10 +02:00
Stephen Abello
3048c8c41f N°5560 - Display an error when trying to regenerate an expired OAuth token 2023-07-06 09:52:00 +02:00
Stephen Abello
246e4a9f50 N°6123 - Fix warnings when launching a backup on MariaDB > v10.6.1 with localhost dbhost 2023-07-06 09:28:01 +02:00
Molkobain
6d58adb6dd N°6359 - Fix JS crash due to new version trying to detect MSIE browser through a dependency that we don't have.
Cherry-picked from f889c53d71
2023-07-05 08:41:00 +02:00
Pierre Goiffon
5a0b5364d6 N°4698 setup/phpinfo.php : if no iTop installation then display a proper message instead of an exception (#265) 2023-06-14 10:18:38 +02:00
Pierre Goiffon
76eed2eba0 N°6098 updateLicenses script : check availability of the required JQ command (#458)
This packaging script requires both bash and the JQ command when running on Windows.
If the later isn't available, it will run without throwing an error...

With this change the script will now check directly at launch for the JQ command availability, and exit in error if it isn't.
2023-06-14 10:17:00 +02:00
Eric Espie
1ec671ef61 N°6351 - code hardening 2023-06-14 09:08:42 +02:00
Eric Espie
72716b7ec8 N°6396 - Protect URL display 2023-06-12 11:36:51 +02:00
Eric Espie
4f999de844 N°6359 - ⬆️ Update jQuery BBQ (from https://github.com/cee-chen/jquery-bbq) 2023-06-08 14:30:09 +02:00
odain
ea49c0a87c enable authent-cas in ci 2023-06-07 21:44:17 +02:00
odain
6cc971849b ci: enhance AddProfile 2023-06-07 21:44:00 +02:00
Pierre Goiffon
2405810864 N°6238 Security hardening 2023-06-07 16:45:35 +02:00
Eric Espie
fff46d99fc N°6358 - Login REST API - renamed test 2023-06-07 15:31:51 +02:00
odain
3a891f707c ci: enhance AddProfile test method to work with any User (not only UserLocal) 2023-06-07 15:06:28 +02:00
odain
8b6ea43ebe N°6358 - Login REST API - fix cas + add tests 2023-06-07 15:05:32 +02:00
Eric Espie
90cf7502e8 N°6358 - Login REST API 2023-06-07 10:09:30 +02:00
Eric Espie
c596fa2967 N°6358 - Login API REST 2023-06-07 09:17:24 +02:00
Timothee
a45177410e N°6350 - Fixing phpunit test 2023-06-06 16:47:06 +02:00
Eric Espie
9e96ea2873 N°6350 - code hardening 2023-06-01 15:35:56 +02:00
Eric Espie
1172159745 N°6351 - code hardening 2023-06-01 15:12:50 +02:00
Pierre Goiffon
fa038ded3d N°6254 ItopDataTestCase::CreateUserRequest : fix new argument default value
Was creating error Too few arguments passed
2023-04-26 16:42:27 +02:00
Pierre Goiffon
e7ea1b831c N°6254 ItopDataTestCase::CreateUserRequest : now pass fields values as array
More versatile way of doing things !
2023-04-26 16:22:26 +02:00
Molkobain
4aff65f98b N°6217 - Add accessiblity meta data for title on "Power menu" 2023-04-25 21:51:32 +02:00
acognet
3c94974d9d N°541 - Dashlets: Improve readability when to much labels (pie chart) or too long labels (bar chart) 2023-04-25 12:09:11 +02:00
Molkobain
fbd72b2783 N°6217 - Add accessiblity meta data for title on "Power menu" 2023-04-20 11:03:43 +02:00
Anne-Catherine
4e95ca3c7b N°541 - Dashlets: Improve readability when to much labels (pie chart) or too long labels (bar chart) (#452)
* N°541 - Dashlets: Improve readability when to much labels (pie chart) or too long labels (bar chart)
2023-04-13 11:23:20 +02:00
Pierre Goiffon
1114ed9562 N°6099 DeadLockLog : improve documentation and use existing constants (#441) 2023-04-12 10:21:34 +02:00
Pierre Goiffon
34368fe795 N°6173 \HTMLSanitizer::Sanitize : Fix handling only svg_sanitizer (#450) 2023-04-11 17:52:41 +02:00
Molkobain
0f016d7511 N°6112 - Dashboard: Improve robustness by trimming dashlet ID returned by server 2023-03-17 15:37:57 +01:00
Pierre Goiffon
5ee6223434 N°5893 Add test for \TriggerOnObject::LogException 2023-03-10 16:04:55 +01:00
Pierre Goiffon
d908827787 N°6016 Setup Wizard : fix MissingDependencyException message logged as html in setup.log
Was the case since e831d66b (commit for parent bug N°5090)
Now we are getting the text version in the log (and still the html one on screen)

The unattended install isn't concerned : it just prints back CheckResult returned by \SetupUtils::CheckSelectedModules, with the exception text message ($e->getMessage())
2023-03-06 11:24:46 +01:00
Pierre Goiffon
4cea418517 N°5893 - Log triggers exception in CRUD stack (#390)
* Log TriggerOnObjectCreate crash

* Log TriggerOnObjectUpdate crash

* Log TriggerOnObjectDelete crash

* Factorize TriggerOnObject log

* \TriggerOnObject::LogException : do not replace not persisted yet object keys
2023-02-28 15:13:28 +01:00
Molkobain
97965277c7 N°6017 - Update OAuth dependencies 2023-02-23 15:57:32 +01:00
Molkobain
18ed5ed526 N°6019 - Increase PHP min version to 7.1.3 to enable dependencies update 2023-02-23 14:53:48 +01:00
Pierre Goiffon
94c4f8c929 N°6016 MissingDependencyException : better log message (#355)
The error displayed on screen was already improved (see #280)
This commit improves the log message we can have for example by running unattended install.
2023-02-23 12:04:56 +01:00
Pierre Goiffon
822922df5c N°5588 - Improve PDF export robustness when AttributeImage dimensions cannot be determined (#350)
Can happen for example on SVG images
Now the export won't crash anymore, and we'll get a log (export channel, warning level) with  the object and attribute causing a problem as context

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-02-23 11:45:29 +01:00
Stephen Abello
cac7e94a67 N°5729 - Fix disabled button in bulk update/transition when picking a value in a drop-down list 2023-02-22 15:42:20 +01:00
Stephen Abello
6d019615d0 N°5865 - Restore DoCheckToWrite error messages in portal 2023-02-22 10:17:34 +01:00
Molkobain
dbd58cfeb6 Tests: Force RestAPI unit tests not to verify SSL certificate as most dev / test envs are self-signed 2023-02-10 23:07:27 +01:00
Pierre Goiffon
f65e14397c N°4660 Fix permissions changes in tests 2023-01-16 11:22:23 +01:00
Pierre Goiffon
c696a81c3a N°5821 JenkinsFile : introduce buildDiscarder 2023-01-12 10:42:06 +01:00
Molkobain
845adf43c6 N°5608 - Harmonize namespaces and merge duplicated test files 2023-01-10 22:36:35 +01:00
Molkobain
5916e4ea39 N°5608 - Ensure both old & new tests structure are ran for extensions for backward compatibility 2023-01-10 22:03:40 +01:00
Molkobain
fbc0a898ae N°5608 - Move test files to corresponding directories after branch rebase 2023-01-10 12:11:12 +01:00
Molkobain
36f8e58e25 N°5608 - Use new ItopTestCase::RequireOnceXXX in unit tests 2023-01-10 12:11:12 +01:00
Molkobain
6a7dbb06b0 N°5608 - Add methods to require_once an iTop or a unit test file to avoid crashes when tests dir is moved 2023-01-10 12:11:12 +01:00
Molkobain
5721a324c1 Tests: Always display test status for better feedback 2023-01-06 22:30:09 +01:00
Molkobain
7de6c72154 Tests: Rename provider method name to match convention 2023-01-06 22:30:09 +01:00
Molkobain
c0cee02351 N°5608 - Factorize all core modules tests to a single test suite 2023-01-06 22:30:09 +01:00
Molkobain
bb674fb873 N°5608 - Move/rename "status" unit tests to match their counterpart location/name 2023-01-06 22:30:09 +01:00
Molkobain
6136eadd31 N°5608 - Fix some broken require paths since move/rename 2023-01-06 22:30:08 +01:00
Molkobain
87cb73c038 N°5608 - Rename "test" folder to "tests" to better match conventions 2023-01-06 22:30:08 +01:00
Molkobain
11d8547cef N°5608 - Move/rename unit tests to match their counterpart location/name 2023-01-06 22:30:08 +01:00
Molkobain
0998c73a1a N°5608 - Add README files 2023-01-06 22:30:07 +01:00
Molkobain
471f66649a N°5608 - Rename unitary test folders for better understanding 2023-01-06 22:30:07 +01:00
Molkobain
e8bf9cf688 N°5608 - Move "twig" PHP unit test to new folder
Notice: Test was not working, still not working
2023-01-06 22:30:07 +01:00
Molkobain
4f88a0e7d2 N°5608 - Move legacy PHP unit tests (not run by CI) to a dedicated folder 2023-01-06 22:30:07 +01:00
Molkobain
c6b0e273e6 N°5608 - Rename "VisualTests" folder to match new convention 2023-01-06 22:30:07 +01:00
Molkobain
d9539f9d01 N°5608 - Add comments to main autoloader 2023-01-06 22:30:06 +01:00
Molkobain
a3e309acb5 N°5608 - Revert "authent-local" test suite to its original rank as it is crashing the CI 🤔 2023-01-06 22:30:06 +01:00
Molkobain
c06cbfd4a9 N°5608 - Rename "coreExtensions" test suite to correct datamodel module (authent-local) 2023-01-06 22:30:06 +01:00
Molkobain
1d7e4e1a42 N°5608 - Move unit tests to a dedicated folder and start reorganizing to match iTop folder structure 2023-01-06 22:30:06 +01:00
Eric Espie
92a36dcfdd 📝 Change packages for auto-documentation 2022-12-29 12:24:56 +01:00
Eric Espie
b37e74b407 📝 Change packages for auto-documentation 2022-12-28 09:51:46 +01:00
Pierre Goiffon
0d49c605e2 💡 Fix \DBSearch::FromOQL phpdoc + modifiers order 2022-12-15 15:36:14 +01:00
Molkobain
7c2f8f4d93 N°5765 - Setup: Never cache folder permissions test response (#374) 2022-12-14 09:33:54 +01:00
Pierre Goiffon
1f76ff940d N°5797 Replace wrong config load (#338) 2022-12-13 18:23:09 +01:00
Eric Espie
bb26e48d38 Update version to next release 2.7.9 2022-12-12 16:19:42 +01:00
Eric Espie
cf433f2f80 N°5725 - Twig update 'filter', 'map' and 'reduce' filters 2022-12-08 08:25:11 +01:00
Eric Espie
ae94e58a43 N°5725 - Twig update 'filter', 'map' and 'reduce' filters 2022-12-07 13:53:15 +01:00
Eric Espie
cda017fa4f N°5725 - Twig update 'filter', 'map' and 'reduce' filters 2022-12-07 13:37:52 +01:00
Pierre Goiffon
dad22f6f83 📄 Update Licenses 2022-12-07 11:04:33 +01:00
Eric Espie
9077f7ba37 N°5430 - OAuth authentication : customize redirect landing URL - remove unnecessary parameter to JS function OAuthConnect 2022-12-02 11:17:01 +01:00
Eric Espie
957ff40f30 N°5155 - Email by SMTP with self-signed certificate (changed default values to the previous behaviour) 2022-12-02 09:25:53 +01:00
Eric Espie
aff9c7748b N°5155 - Email by SMTP with self-signed certificate 2022-11-30 14:18:11 +01:00
Eric Espie
e518d34bc9 N°5553 - OAuth 2 : Hide Client Secret
* client_id is now 255 chars (AttributeString)
 * client_secret is now 64 chars (AttributePassword) and cannot be anymore in the uniqueness rules
 * The modification of redirect_url, client_id or client_secret change the status to inactive and generate a session message to ask for token regeneration
2022-11-30 14:15:37 +01:00
Eric Espie
f0141530b9 N°5725 - Twig update 'filter', 'map' and 'reduce' filters (+1 squashed commits)
Squashed commits:

[00148dec5] N°5725 - Twig update 'filter', 'map' and 'reduce' filters
2022-11-30 13:28:33 +01:00
xtophe38
ce5096a896 N°5758 Change setup test for GDPR consent (#336)
We were using SetupUtils::IsProductVersion, but this was blocking for certain packages like TeemIP standalone.
After this change we are now relying on a new method : \SetupUtils::IsConnectableToITopHub. It will check the iTop Hub Connector module presence instead.
2022-11-29 19:00:17 +01:00
Pierre Goiffon
23e0ed5e56 N°4449 Test for FPD detection in RuntimeDashboard 2022-11-29 18:10:17 +01:00
Pierre Goiffon
d412a52fcc N°4449 Fix FPD in dashboard export/import 2022-11-29 18:10:17 +01:00
Molkobain
3e18ad590f Fix image attributes not being visible in PDF exports 2022-11-25 19:30:35 +01:00
Eric Espie
22111bf667 N°5611 - Fix missing composer files in itop-oauth-client 2022-11-24 14:32:51 +01:00
Eric Espie
6d0c46595d N°5611 - Fix missing composer files in itop-oauth-client 2022-11-24 14:27:42 +01:00
Eric Espie
d292a6b0c3 N°5333 - OAuth and iTop url change 2022-11-24 13:55:36 +01:00
Eric Espie
74702c8d06 N°5430 - OAuth authentication : customize redirect landing URL 2022-11-24 13:55:36 +01:00
Pierre Goiffon
e9c91d986d 📝 CONTRIBUTING : fix typo (stash in stead of squash)
Thanks Molkobain ! (https://github.com/Combodo/iTop/pull/371#discussion_r1030759606)
2022-11-24 09:00:32 +01:00
Eric Espie
70a6b276ca Merge branch 'issue/5685-UpgradeApereoPHPCas' into support/2.7 2022-11-23 15:58:36 +01:00
Eric Espie
f77361ceb2 N°5685 - Upgrade apereo/phpcas 2022-11-23 15:53:43 +01:00
Eric Espie
75f4751b82 N°5741 - remove use of get_config_parameter in Twig 2022-11-23 15:09:20 +01:00
Romain Quetiez
b56f2f56f1 N°5704 - Fix compatibility with PHP <7.3 (HEREDOC syntax) 2022-11-16 17:12:53 +01:00
Eric Espie
68d44fa981 N°5724 - code hardening 2022-11-16 09:32:47 +01:00
Eric Espie
7e5307bd96 N°4867 - "Twig content not allowed" error 2022-11-16 09:31:42 +01:00
Romain Quetiez
cd010afb48 N°5704 - Unit tests on XML assembly (#329)
* Add a complete test suite for XML assembly

* Add a complete test suite for XML assembly

* Dispatched the test of GetDelta into real unit tests

* Add test for GetDelta on a rename operation

* Add comments on a weird case and a case on rename

* Update XML version after rebase from develop to support/2.7

* Fix phpdoc about coverage

* Remove ModelFactory::GetRootDirs and ItopTestCase::RecurseRmDir+CreateTmpDir+RecurseMkDir+RecurseCopy, that were meant to be introduced in iTop 3.0 and have been copied here by mistake, when rebasing the branch from develop to 2.7.0

* Update test/ItopTestCase.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update test/setup/ModelFactoryTest.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update test/ItopTestCase.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

Co-authored-by: Pierre Goiffon <pierre.goiffon@combodo.com>
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2022-11-08 19:43:07 +01:00
Pierre Goiffon
0cf8d731bb Rename iTopDesignFormat test class 2022-11-08 15:59:14 +01:00
Pierre Goiffon
189ca3c555 🚚 Move visual test file to the dedicated directory 2022-10-11 14:28:44 +02:00
Pierre Goiffon
1e1f1f78bf 📝 Backup : more details on check-backup parameters 2022-10-03 14:41:44 +02:00
Pierre Goiffon
1494604740 📝 Backup : move info from wiki to distrib file 2022-10-03 14:35:14 +02:00
odain
0001e8ffc4 💚 use new ci validation 2020-10-09 10:13:51 +02:00
397 changed files with 11153 additions and 4336 deletions

10
.gitignore vendored
View File

@@ -8,7 +8,7 @@
# composer reserver directory, from sources, populate/update using "composer install"
vendor/*
test/vendor/*
tests/*/vendor/*
# all conf but listing prevention
/conf/**
@@ -24,7 +24,9 @@ test/vendor/*
# iTop extensions
/extensions/**
!/extensions/.htaccess
!/extensions/readme.txt
!/extensions/web.config
# all logs but listing prevention
/log/**
@@ -32,8 +34,10 @@ test/vendor/*
!/log/index.php
!/log/web.config
# PHPUnit cache file
/test/.phpunit.result.cache
# PHPUnit: Cache file, local XML working copies
/tests/php-unit-tests/.phpunit.result.cache
/tests/php-unit-tests/phpunit.xml
/tests/php-unit-tests/postbuild_integration.xml
# Jetbrains

View File

@@ -19,17 +19,24 @@
* The target license file path is in `$xmlFilePath`
*/
$iTopFolder = __DIR__ . "/../../" ;
$xmlFilePath = $iTopFolder . "setup/licenses/community-licenses.xml";
$iTopFolder = __DIR__."/../../";
$xmlFilePath = $iTopFolder."setup/licenses/community-licenses.xml";
function get_scope($product_node)
{
$jqExec = shell_exec("jq -V"); // a param is mandatory otherwise the script will freeze
if ((null === $jqExec) || (false === $jqExec)) {
echo "/!\ JQ is required but cannot be launched :( \n";
echo "Check this script PHPDoc block for instructions\n";
die(-1);
}
function get_scope($product_node) {
$scope = $product_node->getAttribute("scope");
if ($scope === "")
{ //put iTop first
if ($scope === "") { //put iTop first
return "aaaaaaaaa";
}
return $scope;
}

View File

@@ -132,7 +132,7 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file
When your code is working, please:
* stash as much as possible your commits,
* squash as much as possible your commits,
* rebase your branch on our repo last commit,
* create a pull request.

8
Jenkinsfile vendored
View File

@@ -1,6 +1,14 @@
def infra
node(){
properties([
buildDiscarder(
logRotator(
daysToKeepStr: "28",
numToKeepStr: "500")
)
])
checkout scm
infra = load '/var/lib/jenkins/workspace/itop-test-infra_master/src/Infra.groovy'

View File

@@ -425,6 +425,12 @@ class UserRightsProfile extends UserRightsAddOnAPI
UR_ACTION_BULK_DELETE => 'bd',
);
/**
* @var array $aUsersProfilesList Cache of users' profiles. Hash array of user ID => [profile ID => profile friendlyname, profile ID => profile friendlyname, ...]
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6887
*/
private $aUsersProfilesList = [];
// Installation: create the very first user
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
{
@@ -654,8 +660,12 @@ class UserRightsProfile extends UserRightsAddOnAPI
$sAction = self::$m_aActionCodes[$iActionCode];
$bStatus = null;
// Cache user's profiles
if(false === array_key_exists($iUser, $this->aUsersProfilesList)){
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
}
// Call the API of UserRights because it caches the list for us
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile)
{
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
if (!is_null($bGrant))
@@ -781,11 +791,16 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Note: this code is VERY close to the code of IsActionAllowed()
$iUser = $oUser->GetKey();
// Cache user's profiles
if(false === array_key_exists($iUser, $this->aUsersProfilesList)){
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
}
// Note: The object set is ignored because it was interesting to optimize for huge data sets
// and acceptable to consider only the root class of the object set
$bStatus = null;
// Call the API of UserRights because it caches the list for us
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile)
{
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
if (!is_null($bGrant))
@@ -814,8 +829,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
}
/**
* Find out which attribute is corresponding the the dimension 'owner org'
* returns null if no such attribute has been found (no filtering should occur)
* @param string $sClass
* @return string|null Find out which attribute is corresponding the dimension 'owner org'
* returns null if no such attribute has been found (no filtering should occur)
*/
public static function GetOwnerOrganizationAttCode($sClass)
{

View File

@@ -604,10 +604,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
/**
* Read and cache organizations allowed to the given user
*
* @param $oUser
* @param $sClass (not used here but can be used in overloads)
* @param User $oUser
* @param string $sClass (not used here but can be used in overloads)
*
* @return array
* @return array keys of the User allowed org
* @throws \CoreException
* @throws \Exception
*/

View File

@@ -42,7 +42,7 @@ class ajax_page extends WebPage implements iTabbedPage
$this->m_sReadyScript = "";
//$this->add_header("Content-type: text/html; charset=utf-8");
$this->no_cache();
$this->add_xframe_options();
$this->add_http_headers();
$this->m_oTabs = new TabManager();
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';
@@ -51,6 +51,16 @@ class ajax_page extends WebPage implements iTabbedPage
utils::InitArchiveMode();
}
/**
* Disabling sending the header so that resource won't be blocked by CORB. See parent method documentation.
* @return void
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation
*/
public function add_xcontent_type_options()
{
// Nothing to do !
}
/**
* @inheritDoc
* @throws \Exception

View File

@@ -29,9 +29,9 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php');
* You may implement such interfaces in a module file (e.g. main.mymodule.php)
*
* @api
* @package LoginExtensibilityAPI
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @package Extensibility
* @since 2.7.0
*/
interface iLoginExtension
@@ -39,12 +39,16 @@ interface iLoginExtension
/**
* Return the list of supported login modes for this plugin
*
* @api
*
* @return array of supported login modes
*/
public function ListSupportedLoginModes();
}
/**
* @api
* @package LoginExtensibilityAPI
* @since 2.7.0
*/
interface iLoginFSMExtension extends iLoginExtension
@@ -56,6 +60,7 @@ interface iLoginFSMExtension extends iLoginExtension
* if LoginWebPage::LOGIN_FSM_RETURN_OK is returned then the login is OK and terminated
* if LoginWebPage::LOGIN_FSM_RETURN_IGNORE is returned then the FSM will proceed to next plugin or state
*
* @api
* @param string $sLoginState (see LoginWebPage::LOGIN_STATE_...)
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
@@ -65,6 +70,8 @@ interface iLoginFSMExtension extends iLoginExtension
}
/**
* @api
* @package LoginExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
@@ -111,6 +118,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
/**
* Initialization
*
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -124,6 +132,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
* Detect login mode explicitly without respecting configured order (legacy mode)
* In most case do nothing here
*
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -140,6 +149,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
* 1 - display login form
* 2 - read the values posted by the user
*
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -153,6 +163,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
* Control the validity of the data provided by the user
* Automatic user provisioning can be done here
*
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -163,6 +174,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
}
/**
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -173,6 +185,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
}
/**
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -183,6 +196,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
}
/**
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -193,6 +207,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
}
/**
* @api
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
@@ -204,22 +219,28 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
}
/**
* @api
* @package LoginExtensibilityAPI
* @since 2.7.0
*/
interface iLogoutExtension extends iLoginExtension
{
/**
* Execute all actions to log out properly
* @api
*/
public function LogoutAction();
}
/**
* @api
* @package UIExtensibilityAPI
* @since 2.7.0
*/
interface iLoginUIExtension extends iLoginExtension
{
/**
* @api
* @return LoginTwigContext
*/
public function GetTwigContext();
@@ -227,18 +248,20 @@ interface iLoginUIExtension extends iLoginExtension
/**
* @api
* @package Extensibility
* @package PreferencesExtensibilityAPI
* @since 2.7.0
*/
interface iPreferencesExtension
{
/**
* @api
* @param \WebPage $oPage
*
*/
public function DisplayPreferences(WebPage $oPage);
/**
* @api
* @param \WebPage $oPage
* @param string $sOperation
*
@@ -251,7 +274,7 @@ interface iPreferencesExtension
* Extend this class instead of implementing iPreferencesExtension if you don't need to overload all methods
*
* @api
* @package Extensibility
* @package PreferencesExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractPreferencesExtension implements iPreferencesExtension
@@ -297,7 +320,7 @@ abstract class AbstractPreferencesExtension implements iPreferencesExtension
* A recommended pattern is to cache data by the mean of static members.
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
*/
interface iApplicationUIExtension
{
@@ -319,6 +342,7 @@ interface iApplicationUIExtension
* }
* </code>
*
* @api
* @param DBObject $oObject The object being displayed
* @param WebPage $oPage The output context
* @param boolean $bEditMode True if the edition form is being displayed
@@ -332,6 +356,7 @@ interface iApplicationUIExtension
*
* The method is called rigth after all the tabs have been displayed
*
* @api
* @param DBObject $oObject The object being displayed
* @param WebPage $oPage The output context
* @param boolean $bEditMode True if the edition form is being displayed
@@ -346,6 +371,7 @@ interface iApplicationUIExtension
* The method is called after the changes from the standard form have been
* taken into account, and before saving the changes into the database.
*
* @api
* @param DBObject $oObject The object being edited
* @param string $sFormPrefix Prefix given to the HTML form inputs
*
@@ -360,6 +386,7 @@ interface iApplicationUIExtension
* javascript into the edition form, and if that code requires to store temporary data
* (this is the case when a file must be uploaded).
*
* @api
* @param string $sTempId Unique temporary identifier made of session_id and transaction_id. It identifies the object in a unique way.
*
* @return void
@@ -371,6 +398,7 @@ interface iApplicationUIExtension
*
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
*
* @api
* @param DBObject $oObject The object being displayed
*
* @return string[] desc
@@ -382,6 +410,7 @@ interface iApplicationUIExtension
*
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
*
* @api
* @param DBObject $oObject The object being displayed
*
* @return string Path of the icon, relative to the modules directory.
@@ -401,6 +430,7 @@ interface iApplicationUIExtension
* * HILIGHT_CLASS_OK
* * HILIGHT_CLASS_NONE
*
* @api
* @param DBObject $oObject The object being displayed
*
* @return integer The value representing the mood of the object
@@ -427,6 +457,7 @@ interface iApplicationUIExtension
*
* See also iPopupMenuExtension for greater flexibility
*
* @api
* @param DBObjectSet $oSet A set of persistent objects (DBObject)
*
* @return string[string]
@@ -438,7 +469,7 @@ interface iApplicationUIExtension
* Extend this class instead of implementing iApplicationUIExtension if you don't need to overload
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
@@ -512,7 +543,7 @@ abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
* or through the GUI.
*
* @api
* @package Extensibility
* @package ORMExtensibilityAPI
*/
interface iApplicationObjectExtension
{
@@ -525,6 +556,7 @@ interface iApplicationObjectExtension
* If the extension returns false, then the framework will perform the usual evaluation.
* Otherwise, the answer is definitively "yes, the object has changed".
*
* @api
* @param \cmdbAbstractObject $oObject The target object
*
* @return boolean True if something has changed for the target object
@@ -537,6 +569,7 @@ interface iApplicationObjectExtension
* The GUI calls this verb and reports any issue.
* Anyhow, this API can be called in other contexts such as the CSV import tool.
*
* @api
* @param \cmdbAbstractObject $oObject The target object
*
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
@@ -550,6 +583,7 @@ interface iApplicationObjectExtension
*
* Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled.
*
* @api
* @param \cmdbAbstractObject $oObject The target object
*
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
@@ -565,6 +599,7 @@ interface iApplicationObjectExtension
* * {@see DBObject::ListPreviousValuesForUpdatedAttributes()} : list of changed attributes and their values before the change
* * {@see DBObject::Get()} : for a given attribute the new value that was persisted
*
* @api
* @param \cmdbAbstractObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
* once for all the changes made within the current page
@@ -580,6 +615,7 @@ interface iApplicationObjectExtension
*
* The method is called right <b>after</b> the object has been written to the database.
*
* @api
* @param \cmdbAbstractObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
* once for all the changes made within the current page
@@ -593,6 +629,7 @@ interface iApplicationObjectExtension
*
* The method is called right <b>before</b> the object will be deleted from the database.
*
* @api
* @param \cmdbAbstractObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
* once for all the changes made within the current page
@@ -606,7 +643,7 @@ interface iApplicationObjectExtension
* Extend this class instead of iApplicationObjectExtension if you don't need to overload all methods
*
* @api
* @package Extensibility
* @package ORMExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractApplicationObjectExtension implements iApplicationObjectExtension
@@ -666,7 +703,7 @@ abstract class AbstractApplicationObjectExtension implements iApplicationObjectE
* by the application, as long as the class definition is included somewhere in the code
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
interface iPopupMenuExtension
@@ -675,18 +712,21 @@ interface iPopupMenuExtension
* Insert an item into the Actions menu of a list
*
* $param is a DBObjectSet containing the list of objects
* @api
*/
const MENU_OBJLIST_ACTIONS = 1;
/**
* Insert an item into the Toolkit menu of a list
*
* $param is a DBObjectSet containing the list of objects
* @api
*/
const MENU_OBJLIST_TOOLKIT = 2;
/**
* Insert an item into the Actions menu on an object details page
*
* $param is a DBObject instance: the object currently displayed
* @api
*/
const MENU_OBJDETAILS_ACTIONS = 3;
/**
@@ -696,12 +736,14 @@ interface iPopupMenuExtension
* is being displayed.
*
* $param is a Dashboard instance: the dashboard currently displayed
* @api
*/
const MENU_DASHBOARD_ACTIONS = 4;
/**
* Insert an item into the User menu (upper right corner)
*
* $param is null
* @api
*/
const MENU_USER_ACTIONS = 5;
/**
@@ -709,6 +751,7 @@ interface iPopupMenuExtension
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on
* the current line)
* @api
*/
const PORTAL_OBJLISTITEM_ACTIONS = 7;
/**
@@ -716,6 +759,7 @@ interface iPopupMenuExtension
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object
* currently displayed)
* @api
*/
const PORTAL_OBJDETAILS_ACTIONS = 8;
@@ -753,6 +797,7 @@ interface iPopupMenuExtension
* This method is called by the framework for each menu.
* The items will be inserted in the menu in the order of the returned array.
*
* @api
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx
* @param mixed $param Depends on $iMenuId, see the constants defined above
*
@@ -765,7 +810,7 @@ interface iPopupMenuExtension
* Base class for the various types of custom menus
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
abstract class ApplicationPopupMenuItem
@@ -826,6 +871,7 @@ abstract class ApplicationPopupMenuItem
}
/**
* @api
* @param $aCssClasses
*/
public function SetCssClasses($aCssClasses)
@@ -836,6 +882,7 @@ abstract class ApplicationPopupMenuItem
/**
* Adds a CSS class to the CSS classes that will be put on the menu item
*
* @api
* @param $sCssClass
*/
public function AddCssClass($sCssClass)
@@ -862,7 +909,7 @@ abstract class ApplicationPopupMenuItem
* Class for adding an item into a popup menu that browses to the given URL
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
class URLPopupMenuItem extends ApplicationPopupMenuItem
@@ -875,6 +922,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
/**
* Constructor
*
* @api
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here
@@ -898,7 +946,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
* Class for adding an item into a popup menu that triggers some Javascript code
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
class JSPopupMenuItem extends ApplicationPopupMenuItem
@@ -950,7 +998,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
* will automatically reduce several consecutive separators to just one
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
@@ -959,6 +1007,7 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
/**
* Constructor
* @api
*/
public function __construct()
{
@@ -976,7 +1025,7 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
* Class for adding an item as a button that browses to the given URL
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
class URLButtonItem extends URLPopupMenuItem
@@ -988,7 +1037,7 @@ class URLButtonItem extends URLPopupMenuItem
* Class for adding an item as a button that runs some JS code
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
class JSButtonItem extends JSPopupMenuItem
@@ -1012,7 +1061,7 @@ class JSButtonItem extends JSPopupMenuItem
* the specified place and can use the passed iTopWebPage object to add javascript or CSS definitions
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.0
*/
interface iPageUIExtension
@@ -1020,6 +1069,7 @@ interface iPageUIExtension
/**
* Add content to the North pane
*
* @api
* @param iTopWebPage $oPage The page to insert stuff into.
*
* @return string The HTML content to add into the page
@@ -1029,6 +1079,7 @@ interface iPageUIExtension
/**
* Add content to the South pane
*
* @api
* @param iTopWebPage $oPage The page to insert stuff into.
*
* @return string The HTML content to add into the page
@@ -1038,6 +1089,7 @@ interface iPageUIExtension
/**
* Add content to the "admin banner"
*
* @api
* @param iTopWebPage $oPage The page to insert stuff into.
*
* @return string The HTML content to add into the page
@@ -1049,7 +1101,7 @@ interface iPageUIExtension
* Extend this class instead of iPageUIExtension if you don't need to overload all methods
*
* @api
* @package Extensibility
* @package UIExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractPageUIExtension implements iPageUIExtension
@@ -1084,7 +1136,7 @@ abstract class AbstractPageUIExtension implements iPageUIExtension
* Implement this interface to add content to any enhanced portal page
*
* @api
* @package Extensibility
* @package PortalExtensibilityAPI
*
* @since 2.4.0 interface creation
* @since 2.7.0 change method signatures due to Silex to Symfony migration
@@ -1098,6 +1150,7 @@ interface iPortalUIExtension
/**
* Returns an array of CSS file urls
*
* @api
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return array
@@ -1107,6 +1160,7 @@ interface iPortalUIExtension
/**
* Returns inline (raw) CSS
*
* @api
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
@@ -1116,6 +1170,7 @@ interface iPortalUIExtension
/**
* Returns an array of JS file urls
*
* @api
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return array
@@ -1125,6 +1180,7 @@ interface iPortalUIExtension
/**
* Returns raw JS code
*
* @api
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
@@ -1134,6 +1190,7 @@ interface iPortalUIExtension
/**
* Returns raw HTML code to put at the end of the <body> tag
*
* @api
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
@@ -1143,6 +1200,7 @@ interface iPortalUIExtension
/**
* Returns raw HTML code to put at the end of the #main-wrapper element
*
* @api
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
@@ -1152,6 +1210,7 @@ interface iPortalUIExtension
/**
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
*
* @api
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
@@ -1163,7 +1222,7 @@ interface iPortalUIExtension
* Extend this class instead of iPortalUIExtension if you don't need to overload all methods
*
* @api
* @package Extensibility
* @package PortalExtensibilityAPI
* @since 2.4.0
*/
abstract class AbstractPortalUIExtension implements iPortalUIExtension
@@ -1229,7 +1288,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
* Implement this interface to add new operations to the REST/JSON web service
*
* @api
* @package Extensibility
* @package RESTExtensibilityAPI
* @since 2.0.1
*/
interface iRestServiceProvider
@@ -1237,6 +1296,7 @@ interface iRestServiceProvider
/**
* Enumerate services delivered by this class
*
* @api
* @param string $sVersion The version (e.g. 1.0) supported by the services
*
* @return array An array of hash 'verb' => verb, 'description' => description
@@ -1246,6 +1306,7 @@ interface iRestServiceProvider
/**
* Enumerate services delivered by this class
*
* @api
* @param string $sVersion The version (e.g. 1.0) supported by the services
* @param string $sVerb
* @param array $aParams
@@ -1259,69 +1320,90 @@ interface iRestServiceProvider
* Minimal REST response structure. Derive this structure to add response data and error codes.
*
* @api
* @package Extensibility
* @package RESTExtensibilityAPI
* @since 2.0.1
*/
class RestResult
{
/**
* Result: no issue has been encountered
* @api
*/
const OK = 0;
/**
* Result: missing/wrong credentials or the user does not have enough rights to perform the requested operation
* @api
*/
const UNAUTHORIZED = 1;
/**
* Result: the parameter 'version' is missing
* @api
*/
const MISSING_VERSION = 2;
/**
* Result: the parameter 'json_data' is missing
* @api
*/
const MISSING_JSON = 3;
/**
* Result: the input structure is not a valid JSON string
* @api
*/
const INVALID_JSON = 4;
/**
* Result: the parameter 'auth_user' is missing, authentication aborted
* @api
*/
const MISSING_AUTH_USER = 5;
/**
* Result: the parameter 'auth_pwd' is missing, authentication aborted
* @api
*/
const MISSING_AUTH_PWD = 6;
/**
* Result: no operation is available for the specified version
* @api
*/
const UNSUPPORTED_VERSION = 10;
/**
* Result: the requested operation is not valid for the specified version
* @api
*/
const UNKNOWN_OPERATION = 11;
/**
* Result: the requested operation cannot be performed because it can cause data (integrity) loss
* @api
*/
const UNSAFE = 12;
/**
* Result: the request page number is not valid. It must be an integer greater than 0
* @api
*/
const INVALID_PAGE = 13;
/**
* Result: the operation could not be performed, see the message for troubleshooting
* @api
*/
const INTERNAL_ERROR = 100;
/**
* Default constructor - ok!
* @api
*/
public function __construct()
{
$this->code = RestResult::OK;
}
/**
* @var int
* @api
*/
public $code;
/**
* @var string
* @api
*/
public $message;
}
@@ -1329,7 +1411,7 @@ class RestResult
* Helpers for implementing REST services
*
* @api
* @package Extensibility
* @package RESTExtensibilityAPI
*/
class RestUtils
{
@@ -1478,6 +1560,7 @@ class RestUtils
/**
* Read and interpret object search criteria from a Rest/Json structure
*
* @api
* @param string $sClass Name of the class
* @param StdClass $oCriteria Hash of attribute code => value (can be a substructure or a scalar, depending on the nature of the
* attriute)
@@ -1538,6 +1621,8 @@ class RestUtils
*
* @return DBObject The object found
* @throws Exception If the input structure is not valid or it could not find exactly one object
*
* @see DBObject::CheckChangedExtKeysValues() generic method to check that we can access the linked object isn't used in that use case because values can be literal, OQL, friendlyname
*/
public static function FindObjectFromKey($sClass, $key, $bAllowNullValue = false)
{
@@ -1587,6 +1672,7 @@ class RestUtils
/**
* Search objects from a polymorph search specification (Rest/Json)
*
* @api
* @param string $sClass Name of the class
* @param mixed $key Either search criteria (substructure), or an object or an OQL string.
* @param int $iLimit The limit of results to return
@@ -1773,4 +1859,28 @@ class RestUtils
interface iModuleExtension
{
public function __construct();
}
/**
* KPI logging extensibility point
*
* KPI Logger extension
*/
interface iKPILoggerExtension
{
/**
* Init the statistics collected
*
* @return void
*/
public function InitStats();
/**
* Add a new KPI to the stats
*
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKPILogData
*
* @return mixed
*/
public function LogOperation($oKPILogData);
}

View File

@@ -4003,7 +4003,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
return $res;
@@ -4020,13 +4022,16 @@ EOF
protected function DBCloneTracked_Internal($newKey = null)
{
$oNewObj = parent::DBCloneTracked_Internal($newKey);
/** @var cmdbAbstractObject $oNewObj */
$oNewObj = MetaModel::GetObject(get_class($this), parent::DBCloneTracked_Internal($newKey));
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
return $oNewObj;
@@ -4054,7 +4059,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
}
}
catch (Exception $e)
@@ -4100,7 +4107,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
}
return parent::DBDeleteTracked_Internal($oDeletionPlan);
@@ -4118,7 +4127,10 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
if ($oExtensionInstance->OnIsModified($this))
$oKPI = new ExecutionKPI();
$bIsModified = $oExtensionInstance->OnIsModified($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
if ($bIsModified)
{
return true;
}
@@ -4162,7 +4174,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
{
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
@@ -4210,7 +4224,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
if (is_array($aNewIssues) && count($aNewIssues) > 0)
{
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
@@ -4722,7 +4738,7 @@ EOF
$bResult = (count($aErrors) == 0);
if ($bResult)
{
list($bResult, $aErrors) = $oObj->CheckToWrite();
[$bResult, $aErrors] = $oObj->CheckToWrite();
}
if ($bPreview)
{
@@ -4748,6 +4764,11 @@ EOF
);
if ($bResult && (!$bPreview))
{
// doing the check will load multiple times same objects :/
// but it shouldn't cost too much on execution time
// user can mitigate by selecting less extkeys/lnk to set and/or less objects to update 🤷‍♂️
$oObj->CheckChangedExtKeysValues();
$oObj->DBUpdate();
}
}

View File

@@ -33,7 +33,7 @@ class CSVPage extends WebPage
parent::__construct($s_title);
$this->add_header("Content-type: text/plain; charset=".self::PAGES_CHARSET);
$this->no_cache();
$this->add_xframe_options();
$this->add_http_headers();
//$this->add_header("Content-Transfer-Encoding: binary");
}

View File

@@ -1193,12 +1193,12 @@ EOF
$sOkButtonLabel = Dict::S('UI:Button:Save');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$sId = addslashes($this->sId);
$sLayoutClass = addslashes($this->sLayoutClass);
$sId = utils::HtmlEntities($this->sId);
$sLayoutClass = utils::HtmlEntities($this->sLayoutClass);
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
$sAutoReloadSec = (string) $this->iAutoReloadSec;
$sTitle = addslashes($this->sTitle);
$sFile = addslashes($this->GetDefinitionFile());
$sTitle = utils::HtmlEntities($this->sTitle);
$sFile = utils::HtmlEntities($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = $this->GetReloadURL();
@@ -1478,6 +1478,29 @@ JS
return $this->sDefinitionFile;
}
/**
* @param string $sDashboardFileRelative can also be an absolute path (compatibility with old URL)
*
* @return string full path to the Dashboard file
* @throws \SecurityException if path isn't under approot
* @uses utils::RealPath()
* @since 2.7.8 3.0.3 3.1.0 N°4449 remove FPD
*/
public static function GetDashboardFileFromRelativePath($sDashboardFileRelative)
{
if (utils::RealPath($sDashboardFileRelative, APPROOT)) {
// compatibility with old URL containing absolute path !
return $sDashboardFileRelative;
}
$sDashboardFile = APPROOT.$sDashboardFileRelative;
if (false === utils::RealPath($sDashboardFile, APPROOT)) {
throw new SecurityException('Invalid dashboard file !');
}
return $sDashboardFile;
}
/**
* @param string $sDefinitionFile
*/

View File

@@ -372,7 +372,7 @@ EOF;
if (!$oPage->IsPrintableVersion())
{
$sMenuTitle = Dict::S('UI:ConfigureThisList');
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><i class="fas fa-tools"></i><i class="fas fa-caret-down"></i><ul>';
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li aria-label="'.Dict::S('UI:Menu:Toolkit').'"><i class="fas fa-tools"></i><i class="fas fa-caret-down"></i><ul>';
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
$aActions = array(

View File

@@ -1009,6 +1009,7 @@ EOF
$iTotalCount = 0;
$aValues = array();
$aURLs = array();
foreach ($aRes as $iRow => $aRow)
{
$sValue = $aRow['grouped_by_1'];
@@ -1016,7 +1017,8 @@ EOF
$aGroupBy[(int)$iRow] = (int) $aRow[$sFctVar];
$iTotalCount += $aRow['_itop_count_'];
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow[$sFctVar]);
// Build the search for this subset
$oSubsetSearch = $this->m_oFilter->DeepClone();
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
@@ -1030,16 +1032,23 @@ EOF
{
case 'bars':
$aNames = array();
$iMaxNbCharsInLabel = 0;
foreach($aValues as $idx => $aValue)
{
$aNames[$idx] = $aValue['label'];
if ($iMaxNbCharsInLabel < mb_strlen($aValue['label'])) {
$iMaxNbCharsInLabel = mb_strlen($aValue['label']);
}
}
$sJSNames = json_encode($aNames);
$sJson = json_encode($aValues);
$oPage->add_ready_script(
<<<EOF
var iChartDefaultHeight = 200,
iChartLegendHeight = 6 * $iMaxNbCharsInLabel,
iChartTotalHeight = iChartDefaultHeight + iChartLegendHeight;
$('#my_chart_$sId').height(iChartTotalHeight+ 'px');
var chart = c3.generate({
bindto: d3.select('#my_chart_$sId'),
data: {
@@ -1107,8 +1116,19 @@ EOF
}
$sJSColumns = json_encode($aColumns);
$sJSNames = json_encode($aNames);
$iNbLinesToAddForName = 0;
if (count($aNames) > 50) {
// Calculation of the number of legends line add to the height of the graph to have a maximum of 5 legend columns
$iNbLinesIncludedInChartHeight = 10;
$iNbLinesToAddForName = ceil(count($aNames) / 5) - $iNbLinesIncludedInChartHeight;
}
$oPage->add_ready_script(
<<<EOF
// Calculate height of graph : 200px (minimum height for the chart) + 20*iNbLinesToAddForName for the legend
var iChartDefaultHeight = 200,
iChartLegendHeight = 20 * $iNbLinesToAddForName,
iChartTotalHeight = (iChartDefaultHeight + iChartLegendHeight);
$('#my_chart_$sId').height(iChartTotalHeight + 'px');
var chart = c3.generate({
bindto: d3.select('#my_chart_$sId'),
data: {
@@ -1915,11 +1935,13 @@ class MenuBlock extends DisplayBlock
{
if (count($aFavoriteActions) > 0)
{
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
$sActionsMenuLabel = Dict::S('UI:Menu:OtherActions');
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li aria-label=\"{$sActionsMenuLabel}\">{$sActionsMenuLabel}<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
}
else
{
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
$sActionsMenuLabel = Dict::S('UI:Menu:Actions');
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li aria-label=\"{$sActionsMenuLabel}\">{$sActionsMenuLabel}<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
}
$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);

View File

@@ -71,7 +71,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
$this->add_header("Content-type: text/html; charset=".self::PAGES_CHARSET);
$this->no_cache();
$this->add_xframe_options();
$this->add_http_headers();
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
@@ -1219,7 +1219,7 @@ EOF;
{
$sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName);
}
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-power-off\"></i><ul>";
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li aria-label=\"" . Dict::S("UI:PowerMenu") . "\"><i class=\"top-right-icon icon-additional-arrow fas fa-power-off\"></i><ul>";
$sLogOffMenu .= "<li><span>$sLogonMessage</span></li>\n";
$aActions = array();

View File

@@ -51,7 +51,7 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
{
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
@@ -67,7 +67,7 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
{
$sAuthUser = $_SESSION['auth_user'];
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
@@ -77,8 +77,13 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
{
$iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
}
LoginWebPage::HTTP401Error();
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -86,7 +91,7 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
{
$_SESSION['can_logoff'] = true;
return LoginWebPage::CheckLoggedUser($iErrorCode);

View File

@@ -77,7 +77,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
{
self::ResetLoginSession();
$iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit == LoginWebPage::EXIT_RETURN)
if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
}
@@ -93,6 +93,12 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
{
if (!isset($_SESSION['login_mode']))
{
// N°6358 - if EXIT_RETURN was asked, send an error
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
// If no plugin validated the user, exit
self::ResetLoginSession();
exit();

View File

@@ -35,7 +35,7 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
{
$sAuthUser = $this->GetAuthUser();
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
@@ -51,7 +51,7 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
{
$sAuthUser = $_SESSION['auth_user'];
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
@@ -61,7 +61,7 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
{
$_SESSION['can_logoff'] = false;
return LoginWebPage::CheckLoggedUser($iErrorCode);
@@ -71,8 +71,13 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
{
$iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
}
LoginWebPage::HTTP401Error();
}
return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -43,6 +43,10 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
exit;
}
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
// No credentials yet, display the form
$oPage = LoginWebPage::NewLoginWebPage();
$oPage->DisplayLoginForm($this->bForceFormOnError);
@@ -62,7 +66,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
@@ -82,7 +86,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
{
$sAuthUser = $_SESSION['auth_user'];
// Store 'auth_user' in session for further use
@@ -96,7 +100,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
{
$this->bForceFormOnError = true;
}
@@ -108,7 +112,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
{
$_SESSION['can_logoff'] = true;
return LoginWebPage::CheckLoggedUser($iErrorCode);

View File

@@ -40,7 +40,7 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnReadCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
{
$_SESSION['login_temp_auth_user'] = utils::ReadParam('auth_user', '', false, 'raw_data');
}
@@ -49,7 +49,7 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
@@ -66,7 +66,7 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
{
$sAuthUser = $_SESSION['auth_user'];
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
@@ -76,7 +76,7 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
{
$this->bErrorOccurred = true;
}

View File

@@ -32,7 +32,7 @@ class LoginWebPage extends NiceWebPage
{
const EXIT_PROMPT = 0;
const EXIT_HTTP_401 = 1;
const EXIT_RETURN = 2;
const EXIT_RETURN = 2; // Non interactive mode (ajax, rest, ...)
const EXIT_CODE_OK = 0;
const EXIT_CODE_MISSINGLOGIN = 1;
@@ -85,7 +85,7 @@ class LoginWebPage extends NiceWebPage
parent::__construct($sTitle);
$this->SetStyleSheet();
$this->no_cache();
$this->add_xframe_options();
$this->add_http_headers();
}
public function SetStyleSheet()
@@ -352,14 +352,20 @@ class LoginWebPage extends NiceWebPage
$this->output();
}
public static function ResetSession()
public static function ResetSession($bFullCleanup = false)
{
// Unset all of the session variables.
unset($_SESSION['auth_user']);
unset($_SESSION['login_state']);
unset($_SESSION['can_logoff']);
unset($_SESSION['archive_mode']);
unset($_SESSION['impersonate_user']);
if ($bFullCleanup) {
// Unset all of the session variables.
foreach (array_keys($_SESSION) as $sKey) {
unset($_SESSION[$sKey]);
}
} else {
unset($_SESSION['auth_user']);
unset($_SESSION['login_state']);
unset($_SESSION['can_logoff']);
unset($_SESSION['archive_mode']);
unset($_SESSION['impersonate_user']);
}
UserRights::_ResetSessionCache();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
@@ -931,7 +937,7 @@ class LoginWebPage extends NiceWebPage
}
else
{
if ($iOnExit == self::EXIT_RETURN)
if ($iOnExit === self::EXIT_RETURN)
{
return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED;
}
@@ -987,7 +993,7 @@ class LoginWebPage extends NiceWebPage
{
if ($bMustBeAdmin && !UserRights::IsAdministrator())
{
if ($iOnExit == self::EXIT_RETURN)
if ($iOnExit === self::EXIT_RETURN)
{
return self::EXIT_CODE_MUSTBEADMIN;
}
@@ -1003,7 +1009,7 @@ class LoginWebPage extends NiceWebPage
}
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
}
if ($iOnExit == self::EXIT_RETURN)
if ($iOnExit === self::EXIT_RETURN)
{
return $iRet;
}

View File

@@ -91,4 +91,10 @@ else
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
try {
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
}
catch (MySQLException $e) {
IssueLog::Debug($e->getMessage());
throw new MySQLException('Could not connect to the DB server', []);
}

View File

@@ -5,7 +5,6 @@ namespace Combodo\iTop;
use AttributeDateTime;
use Dict;
use Exception;
use MetaModel;
use Twig_Environment;
use Twig_SimpleFilter;
use Twig_SimpleFunction;
@@ -115,14 +114,6 @@ class TwigExtension
return utils::IsDevelopmentEnvironment();
}));
// Function to get configuration parameter
// Usage in twig: {{ get_config_parameter('foo') }}
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_config_parameter', function($sParamName)
{
$oConfig = MetaModel::GetConfig();
return $oConfig->Get($sParamName);
}));
// Function to get the URL of a static page in a module
// Usage in twig: {{ get_static_page_module_url('itop-my-module', 'path-to-my-page') }}
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_static_page_module_url', function($sModuleName, $sPage)

View File

@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Service\Module\ModuleService;
use ScssPhp\ScssPhp\Compiler;
@@ -44,6 +45,74 @@ class FileUploadException extends Exception
*/
class utils
{
/**
* @var string
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_INTEGER = 'integer';
/**
* Datamodel class
* @var string
* @since 2.7.10 3.0.0
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606 update PHPDoc
* @uses MetaModel::IsValidClass()
*/
public const ENUM_SANITIZATION_FILTER_CLASS = 'class';
/**
* @var string
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606
* @uses class_exists()
*/
public const ENUM_SANITIZATION_FILTER_PHP_CLASS = 'php_class';
/**
* @var string
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_STRING = 'string';
/**
* @var string
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param';
/**
* @var string
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_PARAMETER = 'parameter';
/**
* @var string
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_FIELD_NAME = 'field_name';
/**
* @var string
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_TRANSACTION_ID = 'transaction_id';
/**
* @var string For XML / HTML node identifiers
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
/**
* @var string
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
/**
* @var string
* @since 3.0.2 3.1.0 N°4899
* @since 2.7.10 N°6606
*/
public const ENUM_SANITIZATION_FILTER_URL = 'url';
/**
* @var string
* @since 3.0.0
* @since 2.7.10 N°6606
*/
public const DEFAULT_SANITIZATION_FILTER = self::ENUM_SANITIZATION_FILTER_RAW_DATA;
/**
* Cache when getting config from disk or set externally (using {@link SetConfig})
* @internal
@@ -274,8 +343,7 @@ class utils
/**
* @param string|string[] $value
* @param string $sSanitizationFilter one of : integer, class, string, context_param, parameter, field_name,
* element_identifier, transaction_id, parameter, raw_data
* @param string $sSanitizationFilter one of utils::ENUM_SANITIZATION_* const
*
* @return string|string[]|bool boolean for :
* * the 'class' filter (true if valid, false otherwise)
@@ -284,16 +352,20 @@ class utils
* @since 2.5.2 2.6.0 new 'transaction_id' filter
* @since 2.7.0 new 'element_identifier' filter
* @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter
* @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
* @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
*
* @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
*/
protected static function Sanitize_Internal($value, $sSanitizationFilter)
{
switch ($sSanitizationFilter)
{
case 'integer':
case static::ENUM_SANITIZATION_FILTER_INTEGER:
$retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT);
break;
case 'class':
case static::ENUM_SANITIZATION_FILTER_CLASS:
$retValue = $value;
if (!MetaModel::IsValidClass($value))
{
@@ -301,14 +373,21 @@ class utils
}
break;
case 'string':
case static::ENUM_SANITIZATION_FILTER_STRING:
$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
break;
case 'context_param':
case 'parameter':
case 'field_name':
case 'transaction_id':
case static::ENUM_SANITIZATION_FILTER_PHP_CLASS:
$retValue = $value;
if (!class_exists($value)) {
$retValue = false;
}
break;
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
if (is_array($value))
{
$retValue = array();
@@ -326,7 +405,7 @@ class utils
{
switch ($sSanitizationFilter)
{
case 'transaction_id':
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
// same as parameter type but keep the dot character
// see N°1835 : when using file transaction_id on Windows you get *.tmp tokens
// it must be included at the regexp beginning otherwise you'll get an invalid character error
@@ -334,18 +413,18 @@ class utils
array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
break;
case 'parameter':
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
// characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
break;
case 'field_name':
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
break;
case 'context_param':
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
break;
@@ -355,17 +434,18 @@ class utils
break;
// For XML / HTML node identifiers
case 'element_identifier':
case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
break;
// For URL
case 'url':
$retValue = filter_var($value, FILTER_SANITIZE_URL);
case static::ENUM_SANITIZATION_FILTER_URL:
// N°6350 - returns only valid URLs
$retValue = filter_var($value, FILTER_VALIDATE_URL);
break;
default:
case 'raw_data':
case static::ENUM_SANITIZATION_FILTER_RAW_DATA:
$retValue = $value;
// Do nothing
}
@@ -1226,13 +1306,23 @@ class utils
}
}
/**
* @return string A path to the folder into which data can be written
* @internal
* @since N°6097 2.7.10 3.0.4 3.1.1
*/
public static function GetDataPath(): string
{
return APPROOT.'data/';
}
/**
* @return string A path to a folder into which any module can store cache data
* The corresponding folder is created or cleaned upon code compilation
*/
public static function GetCachePath()
{
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
return static::GetDataPath().'cache-'.MetaModel::GetEnvironment().'/';
}
/**
* @return string A path to a folder into which any module can store log
@@ -1329,19 +1419,19 @@ class utils
$oDashboard = $param;
$sDashboardId = $oDashboard->GetId();
$sDashboardFile = $oDashboard->GetDefinitionFile();
$sDashboardFileRelative = utils::LocalPath($sDashboardFile);
$sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
$sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
$sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
$sDashboardFileJS = addslashes($sDashboardFile);
$sDashboardFileURL = urlencode($sDashboardFile);
$sDashboardFileJS = addslashes($sDashboardFileRelative);
$sDashboardFileURL = urlencode($sDashboardFileRelative);
$sUploadDashboardTransactId = utils::GetNewTransactionId();
$aResult = array(
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sDashboardId.'&file='.$sDashboardFileURL),
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sDashboardId', file: '$sDashboardFileJS', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn', transaction: '$sUploadDashboardTransactId' })"),
);
if ($oDashboard->GetReloadURL())
{
if ($oDashboard->GetReloadURL()) {
$aResult[] = new SeparatorPopupMenuItem();
$aResult[] = new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $oDashboard->GetReloadURL().'&printable=1', '_blank');
}
@@ -1945,24 +2035,7 @@ class utils
*/
public static function GetCurrentModuleName($iCallDepth = 0)
{
$sCurrentModuleName = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleName = $sModuleName;
break;
}
}
}
return $sCurrentModuleName;
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
}
/**
@@ -1978,24 +2051,7 @@ class utils
*/
public static function GetCurrentModuleDir($iCallDepth)
{
$sCurrentModuleDir = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleDir = basename($sRootDir);
break;
}
}
}
return $sCurrentModuleDir;
return ModuleService::GetInstance()->GetCurrentModuleDir($iCallDepth);
}
/**
@@ -2010,12 +2066,7 @@ class utils
*/
public static function GetCurrentModuleUrl()
{
$sDir = static::GetCurrentModuleDir(1);
if ( $sDir !== '')
{
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
}
return '';
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
}
/**
@@ -2025,8 +2076,7 @@ class utils
*/
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
{
$sModuleName = static::GetCurrentModuleName(1);
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
}
/**
@@ -2035,12 +2085,7 @@ class utils
*/
public static function GetCompiledModuleVersion($sModuleName)
{
$aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
}
/**
@@ -2295,6 +2340,38 @@ class utils
return in_array($sClass, $aHugeClasses);
}
/**
* Helper around the native strlen() PHP method to test a string for null or empty value
*
* @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated"
*
* @param string|null $sString
*
* @return bool if string null or empty
* @since 3.0.2 N°5302
* @since 2.7.10 N°6458 add method in the 2.7 branch
*/
public static function IsNullOrEmptyString(?string $sString): bool
{
return $sString === null || strlen($sString) === 0;
}
/**
* Helper around the native strlen() PHP method to test a string not null or empty value
*
* @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated"
*
* @param string|null $sString
*
* @return bool if string is not null and not empty
* @since 3.0.2 N°5302
* @since 2.7.10 N°6458 add method in the 2.7 branch
*/
public static function IsNotNullOrEmptyString(?string $sString): bool
{
return !static::IsNullOrEmptyString($sString);
}
/**
* Check if iTop is in a development environment (VCS vs build number)
*
@@ -2504,4 +2581,5 @@ class utils
return (substr(PHP_OS,0,3) === 'WIN');
}
}

View File

@@ -483,12 +483,24 @@ class WebPage implements Page
}
/**
* @param string|null $sHeaderValue for example `SAMESITE`. If null will set the header using the config parameter value.
* @param string|null $sXFrameOptionsHeaderValue passed to {@see add_xframe_options}
*
* @return void
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation, replace {@see add_xframe_options} consumers call
*/
public function add_http_headers($sXFrameOptionsHeaderValue = null)
{
$this->add_xframe_options($sXFrameOptionsHeaderValue);
$this->add_xcontent_type_options();
}
/**
* @param string|null $sHeaderValue for example `SAMESITE`. If null will set the header using the `security_header_xframe` config parameter value.
*
* @since 2.7.3 3.0.0 N°3416
* @uses security_header_xframe config parameter
* @uses \utils::GetConfig()
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
*
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options HTTP header MDN documentation
*/
public function add_xframe_options($sHeaderValue = null)
{
@@ -499,6 +511,38 @@ class WebPage implements Page
$this->add_header('X-Frame-Options: '.$sHeaderValue);
}
/**
* Warning : this header will trigger the Cross-Origin Read Blocking (CORB) protection for some mime types (HTML, XML except SVG, JSON, text/plain)
* In consequence some children pages will override this method.
*
* Sending header can be disabled globally using the `security.enable_header_xcontent_type_options` optional config parameter.
*
* @return void
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation
*
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options HTTP header MDN documentation
* @link https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md#determining-whether-a-response-is-corb_protected "Determining whether a response is CORB-protected"
*/
public function add_xcontent_type_options()
{
try {
$oConfig = utils::GetConfig();
} catch (ConfigException|CoreException $e) {
$oConfig = null;
}
if (is_null($oConfig)) {
$bSendXContentTypeOptionsHttpHeader = true;
} else {
$bSendXContentTypeOptionsHttpHeader = $oConfig->Get('security.enable_header_xcontent_type_options');
}
if ($bSendXContentTypeOptionsHttpHeader === false) {
return;
}
$this->add_header('X-Content-Type-Options: nosniff');
}
/**
* Add needed headers to the page so that it will no be cached
*/

View File

@@ -44,10 +44,20 @@ class XMLPage extends WebPage
$this->m_bHeaderSent = false;
$this->add_header("Content-type: text/xml; charset=".self::PAGES_CHARSET);
$this->no_cache();
$this->add_xframe_options();
$this->add_http_headers();
$this->add_header("Content-location: export.xml");
}
/**
* Disabling sending the header so that resource won't be blocked by CORB. See parent method documentation.
* @return void
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation
*/
public function add_xcontent_type_options()
{
// Nothing to do !
}
public function output()
{
if (!$this->m_bPassThrough)

View File

@@ -14,7 +14,7 @@ define('APPCONF', APPROOT.'conf/');
* @used-by utils::GetItopVersionWikiSyntax()
* @used-by iTopModulesPhpVersionIntegrationTest
*/
define('ITOP_CORE_VERSION', '2.7.8');
define('ITOP_CORE_VERSION', '2.7.10');
require_once APPROOT.'bootstrap.inc.php';

View File

@@ -22,14 +22,8 @@ define('ITOP_DEFAULT_ENV', 'production');
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
if (function_exists('microtime'))
{
$fItopStarted = microtime(true);
}
else
{
$fItopStarted = 1000 * time();
}
$fItopStarted = microtime(true);
$iItopInitialMemory = memory_get_usage(true);
if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false)
{

View File

@@ -4,7 +4,7 @@
"type": "project",
"license": "AGPL-3.0-only",
"require": {
"php": ">=7.0.8",
"php": ">=7.1.3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -13,7 +13,9 @@
"ext-mysqli": "*",
"ext-soap": "*",
"combodo/tcpdf": "~6.4.4",
"firebase/php-jwt": "~6.4.0",
"guzzlehttp/guzzle": "^6.5.8",
"guzzlehttp/psr7": "~1.9.1",
"laminas/laminas-mail": "^2.11",
"laminas/laminas-servicemanager": "^3.5",
"league/oauth2-google": "^3.0",
@@ -44,7 +46,7 @@
},
"config": {
"platform": {
"php": "7.0.8"
"php": "7.1.3"
},
"vendor-dir": "lib",
"preferred-install": {
@@ -60,6 +62,7 @@
"sources/application",
"sources/Composer",
"sources/Controller",
"sources/Service",
"sources/Core"
],
"exclude-from-classmap": [

63
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8415e71a4288813b5a5d82ec4a00216b",
"content-hash": "abee0f7bd244530a88b08e1e338b0ec5",
"packages": [
{
"name": "combodo/tcpdf",
@@ -254,25 +254,31 @@
},
{
"name": "firebase/php-jwt",
"version": "v5.5.1",
"version": "v6.4.0",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
"php": "^7.1||^8.0"
},
"require-dev": {
"phpunit/phpunit": ">=4.8 <=9"
"guzzlehttp/guzzle": "^6.5||^7.4",
"phpspec/prophecy-phpunit": "^1.1",
"phpunit/phpunit": "^7.5||^9.5",
"psr/cache": "^1.0||^2.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-sodium": "Support EdDSA (Ed25519) signatures",
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
@@ -305,9 +311,9 @@
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v5.5.1"
"source": "https://github.com/firebase/php-jwt/tree/v6.4.0"
},
"time": "2021-11-08T20:18:51+00:00"
"time": "2023-02-09T21:01:23+00:00"
},
{
"name": "guzzlehttp/guzzle",
@@ -510,16 +516,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "1.9.0",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318"
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
"shasum": ""
},
"require": {
@@ -538,11 +544,6 @@
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
@@ -600,7 +601,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/1.9.0"
"source": "https://github.com/guzzle/psr7/tree/1.9.1"
},
"funding": [
{
@@ -616,7 +617,7 @@
"type": "tidelift"
}
],
"time": "2022-06-20T21:43:03+00:00"
"time": "2023-04-17T16:00:37+00:00"
},
{
"name": "laminas/laminas-loader",
@@ -4333,22 +4334,24 @@
},
{
"name": "thenetworg/oauth2-azure",
"version": "v2.0.1",
"version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/TheNetworg/oauth2-azure.git",
"reference": "2649422a0dc74af32d21d9d738d37abcd5b03998"
"reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/2649422a0dc74af32d21d9d738d37abcd5b03998",
"reference": "2649422a0dc74af32d21d9d738d37abcd5b03998",
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/06fb2d620fb6e6c934f632c7ec7c5ea2e978a844",
"reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844",
"shasum": ""
},
"require": {
"firebase/php-jwt": "~3.0||~4.0||~5.0",
"ext-json": "*",
"ext-openssl": "*",
"firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0",
"league/oauth2-client": "~2.0",
"php": "^5.6|^7.0|^8.0"
"php": "^7.1|^8.0"
},
"type": "library",
"autoload": {
@@ -4382,9 +4385,9 @@
],
"support": {
"issues": "https://github.com/TheNetworg/oauth2-azure/issues",
"source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.0.1"
"source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.1.1"
},
"time": "2021-01-11T12:20:12+00:00"
"time": "2022-06-23T10:35:36+00:00"
},
{
"name": "true/punycode",
@@ -4736,7 +4739,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.0.8",
"php": ">=7.1.3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -4747,7 +4750,7 @@
},
"platform-dev": [],
"platform-overrides": {
"php": "7.0.8"
"php": "7.1.3"
},
"plugin-api-version": "2.3.0"
}

View File

@@ -67,8 +67,7 @@ class MyHelpers
// format sss.mmmuuupppnnn
public static function getmicrotime()
{
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
return microtime(true);
}
/*
@@ -420,6 +419,7 @@ class MyHelpers
//}
return $sOutput;
}
}
/**
@@ -524,5 +524,3 @@ class Str
return (strtolower($sString) == $sString);
}
}
?>

View File

@@ -149,7 +149,9 @@ abstract class BulkExport
$this->oSearch = null;
$this->iChunkSize = 0;
$this->sFormatCode = null;
$this->aStatusInfo = array();
$this->aStatusInfo = [
'show_obsolete_data' => utils::ShowObsoleteData(),
];
$this->oBulkExportResult = null;
$this->sTmpFile = '';
$this->bLocalizeOutput = false;
@@ -203,15 +205,17 @@ abstract class BulkExport
if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId()))
{
$sFormatCode = $oInfo->Get('format');
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
$aStatusInfo = json_decode($oInfo->Get('status_info'),true);
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
$oSearch->SetShowObsoleteData($aStatusInfo['show_obsolete_data']);
$oBulkExporter = self::FindExporter($sFormatCode, $oSearch);
if ($oBulkExporter)
{
$oBulkExporter->SetFormat($sFormatCode);
$oBulkExporter->SetObjectList($oSearch);
$oBulkExporter->SetChunkSize($oInfo->Get('chunk_size'));
$oBulkExporter->SetStatusInfo(json_decode($oInfo->Get('status_info'), true));
$oBulkExporter->SetStatusInfo($aStatusInfo);
$oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output'));
@@ -289,6 +293,7 @@ abstract class BulkExport
*/
public function SetObjectList(DBSearch $oSearch)
{
$oSearch->SetShowObsoleteData($this->aStatusInfo['show_obsolete_data']);
$this->oSearch = $oSearch;
}

View File

@@ -134,6 +134,12 @@ class CMDBSource
const ENUM_DB_VENDOR_MARIADB = 'MariaDB';
const ENUM_DB_VENDOR_PERCONA = 'Percona';
/**
* @since 2.7.10 3.0.4 3.1.2 3.0.2 N°6889 constant creation
* @internal will be removed in a future version
*/
const MYSQL_DEFAULT_PORT = 3306;
/**
* Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
* Message: Lock wait timeout exceeded; try restarting transaction
@@ -319,16 +325,19 @@ class CMDBSource
/**
* @param string $sDbHost initial value ("p:domain:port" syntax)
* @param string $sServer server variable to update
* @param int $iPort port variable to update
* @param int|null $iPort port variable to update, will return null if nothing is specified in $sDbHost
*
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°6889 will return null in $iPort if port isn't present in $sDbHost. Use {@see MYSQL_DEFAULT_PORT} if needed
*
* @link http://php.net/manual/en/mysqli.persistconns.php documentation for the "p:" prefix (persistent connexion)
*/
public static function InitServerAndPort($sDbHost, &$sServer, &$iPort)
{
$aConnectInfo = explode(':', $sDbHost);
$bUsePersistentConnection = false;
if (strcasecmp($aConnectInfo[0], 'p') == 0)
if (strcasecmp($aConnectInfo[0], 'p') === 0)
{
// we might have "p:" prefix to use persistent connections (see http://php.net/manual/en/mysqli.persistconns.php)
$bUsePersistentConnection = true;
$sServer = $aConnectInfo[0].':'.$aConnectInfo[1];
}
@@ -346,10 +355,6 @@ class CMDBSource
{
$iPort = (int)($aConnectInfo[1]);
}
else
{
$iPort = 3306;
}
}
/**
@@ -535,6 +540,7 @@ class CMDBSource
{
self::$m_sDBName = '';
}
self::_TablesInfoCacheReset(); // reset the table info cache!
}
public static function CreateTable($sQuery)
@@ -731,8 +737,9 @@ class CMDBSource
{
self::LogDeadLock($e);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
} finally {
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
}
if ($oResult === false)
{
$aContext = array('query' => $sSql);

View File

@@ -555,6 +555,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.allow_self_signed' => array(
'type' => 'bool',
'description' => 'Allow self signed peer certificates',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.verify_peer' => array(
'type' => 'bool',
'description' => 'Verify peer certificate',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_css' => array(
'type' => 'string',
'description' => 'CSS that will override the standard stylesheet used for the notifications',
@@ -947,6 +963,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_kpi_generate_legacy_report' => array(
'type' => 'bool',
'description' => 'Generate the legacy KPI report (kpi.html)',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'max_linkset_output' => array(
'type' => 'integer',
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
@@ -1296,6 +1320,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.enable_header_xcontent_type_options' => [
'type' => 'bool',
'description' => 'If set to false, iTop will stop sending the X-Content-Type-Options HTTP header. This header could trigger CORB protection on certain resources (JSON, XML, HTML, text) therefore blocking them.',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',

View File

@@ -229,12 +229,47 @@ class CoreUnexpectedValue extends CoreException
{
}
/**
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6458 object creation
*/
class InvalidExternalKeyValueException extends CoreUnexpectedValue
{
private const ENUM_PARAMS_OBJECT = 'current_object';
private const ENUM_PARAMS_ATTCODE = 'attcode';
private const ENUM_PARAMS_ATTVALUE = 'attvalue';
private const ENUM_PARAMS_USER = 'current_user';
public function __construct($oObject, $sAttCode, $aContextData = null, $oPrevious = null)
{
$aContextData[self::ENUM_PARAMS_OBJECT] = get_class($oObject) . '::' . $oObject->GetKey();
$aContextData[self::ENUM_PARAMS_ATTCODE] = $sAttCode;
$aContextData[self::ENUM_PARAMS_ATTVALUE] = $oObject->Get($sAttCode);
$oCurrentUser = UserRights::GetUserObject();
if (false === is_null($oCurrentUser)) {
$aContextData[self::ENUM_PARAMS_USER] = get_class($oCurrentUser) . '::' . $oCurrentUser->GetKey();
}
parent::__construct('Attribute pointing to an object that is either non existing or not readable by the current user', $aContextData, '', $oPrevious);
}
public function GetAttCode(): string
{
return $this->getContextData()[self::ENUM_PARAMS_ATTCODE];
}
public function GetAttValue(): string
{
return $this->getContextData()[self::ENUM_PARAMS_ATTVALUE];
}
}
class SecurityException extends CoreException
{
}
/**
* Throwned when querying on an object that exists in the database but is archived
* Thrown when querying on an object that exists in the database but is archived
*
* @see N.1108
* @since 2.5.1

View File

@@ -188,8 +188,8 @@ final class ItopCounter
if (!$hDBLink)
{
throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
}
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
}
return $hDBLink;
}

View File

@@ -15,6 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\ExportHelper;
/**
* Bulk export: CSV export
@@ -113,6 +114,7 @@ class CSVBulkExport extends TabularBulkExport
case 'csv_options':
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:CSVOptions').'</legend>');
$oP->add(ExportHelper::GetAlertForExcelMaliciousInjection());
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
$oP->add('<h3>'.Dict::S('UI:CSVImport:SeparatorCharacter').'</h3>');
$sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data');

View File

@@ -2140,7 +2140,6 @@ abstract class DBObject implements iDisplay
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
*/
public function DoCheckToWrite()
{
@@ -2195,7 +2194,6 @@ abstract class DBObject implements iDisplay
}
/**
*
* @api
* @api-advanced
*
@@ -2211,7 +2209,6 @@ abstract class DBObject implements iDisplay
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
*/
final public function CheckToWrite()
{
@@ -2225,7 +2222,7 @@ abstract class DBObject implements iDisplay
$oKPI = new ExecutionKPI();
$this->DoCheckToWrite();
$oKPI->ComputeStats('CheckToWrite', get_class($this));
$oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite');
if (count($this->m_aCheckIssues) == 0)
{
$this->m_bCheckStatus = true;
@@ -2238,6 +2235,87 @@ abstract class DBObject implements iDisplay
return array($this->m_bCheckStatus, $this->m_aCheckIssues, $this->m_bSecurityIssue);
}
/**
* Checks for extkey attributes values. This will throw exception on non-existing as well as non-accessible objects (silo, scopes).
* That's why the test is done for all users including Administrators
*
* Note that due to perf issues, this isn't called directly by the ORM, but has to be called by consumers when possible.
*
* @param callable(string, string):bool|null $oIsObjectLoadableCallback Override to check if object is accessible.
* Parameters are object class and key
* Return value should be false if cannot access object, true otherwise
* @return void
*
* @throws ArchivedObjectException
* @throws CoreException if cannot get object attdef list
* @throws CoreUnexpectedValue
* @throws InvalidExternalKeyValueException
* @throws MySQLException
* @throws SecurityException if one extkey is pointing to an invalid value
*
* @link https://github.com/Combodo/iTop/security/advisories/GHSA-245j-66p9-pwmh
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6458
*
* @see \RestUtils::FindObjectFromKey for the same check in the REST endpoint
*/
final public function CheckChangedExtKeysValues(callable $oIsObjectLoadableCallback = null)
{
if (is_null($oIsObjectLoadableCallback)) {
$oIsObjectLoadableCallback = function ($sClass, $sId) {
$oRemoteObject = MetaModel::GetObject($sClass, $sId, false);
if (is_null($oRemoteObject)) {
return false;
}
return true;
};
}
$aChanges = $this->ListChanges();
$aAttCodesChanged = array_keys($aChanges);
foreach ($aAttCodesChanged as $sAttDefCode) {
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttDefCode);
if ($oAttDef instanceof AttributeLinkedSetIndirect) {
/** @var ormLinkSet $oOrmSet */
$oOrmSet = $this->Get($sAttDefCode);
while ($oLnk = $oOrmSet->Fetch()) {
$oLnk->CheckChangedExtKeysValues($oIsObjectLoadableCallback);
}
continue;
}
/** @noinspection PhpConditionCheckedByNextConditionInspection */
/** @noinspection NotOptimalIfConditionsInspection */
if (($oAttDef instanceof AttributeHierarchicalKey) || ($oAttDef instanceof AttributeExternalKey)) {
$sRemoteObjectClass = $oAttDef->GetTargetClass();
$sRemoteObjectKey = $this->Get($sAttDefCode);
} else if ($oAttDef instanceof AttributeObjectKey) {
$sRemoteObjectClassAttCode = $oAttDef->Get('class_attcode');
$sRemoteObjectClass = $this->Get($sRemoteObjectClassAttCode);
$sRemoteObjectKey = $this->Get($sAttDefCode);
} else {
continue;
}
if (utils::IsNullOrEmptyString($sRemoteObjectClass)
|| utils::IsNullOrEmptyString($sRemoteObjectKey)
) {
continue;
}
// 0 : Undefined ext. key (EG. non-mandatory and no value provided)
// < 0 : Non yet persisted object
/** @noinspection TypeUnsafeComparisonInspection Non-strict comparison as object ID can be string */
if ($sRemoteObjectKey <= 0) {
continue;
}
if (false === $oIsObjectLoadableCallback($sRemoteObjectClass, $sRemoteObjectKey)) {
throw new InvalidExternalKeyValueException($this, $sAttDefCode);
}
}
}
/**
* Check if it is allowed to delete the existing object from the database
*
@@ -2383,13 +2461,13 @@ abstract class DBObject implements iDisplay
* @api
* @api-advanced
*
* @see \DBObject::ListPreviousValuesForUpdatedAttributes() to get previous values anywhere in the CRUD stack
* @see https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Asequence_crud iTop CRUD stack documentation
* @return array attname => currentvalue List the attributes that have been changed using {@see DBObject::Set()}.
* @return array attcode => currentvalue List the attributes that have been changed using {@see DBObject::Set()}.
* Reset during {@see DBObject::DBUpdate()}
* @throws Exception
* @see \DBObject::ListPreviousValuesForUpdatedAttributes() to get previous values anywhere in the CRUD stack
* @see https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Asequence_crud iTop CRUD stack documentation
* @uses m_aCurrValues
*/
*/
public function ListChanges()
{
if ($this->m_bIsInDB)
@@ -2680,7 +2758,6 @@ abstract class DBObject implements iDisplay
* @throws \Exception
*
* @internal
*
*/
public function DBInsertNoReload()
{
@@ -2693,8 +2770,12 @@ abstract class DBObject implements iDisplay
$sRootClass = MetaModel::GetRootClass($sClass);
// Ensure the update of the values (we are accessing the data directly)
$oKPI = new ExecutionKPI();
$this->DoComputeValues();
$oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
$oKPI = new ExecutionKPI();
$this->OnInsert();
$oKPI->ComputeStatsForExtension($this, 'OnInsert');
if ($this->m_iKey < 0)
{
@@ -2712,7 +2793,7 @@ abstract class DBObject implements iDisplay
}
// Ultimate check - ensure DB integrity
list($bRes, $aIssues) = $this->CheckToWrite();
[$bRes, $aIssues] = $this->CheckToWrite();
if (!$bRes)
{
throw new CoreCannotSaveObjectException(array('issues' => $aIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
@@ -2818,7 +2899,9 @@ abstract class DBObject implements iDisplay
$this->m_aOrigValues[$sAttCode] = $value;
}
$oKPI = new ExecutionKPI();
$this->AfterInsert();
$oKPI->ComputeStatsForExtension($this, 'AfterInsert');
// Activate any existing trigger
$sClass = get_class($this);
@@ -2826,13 +2909,14 @@ abstract class DBObject implements iDisplay
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectCreate AS t WHERE t.target_class IN (:class_list)"), array(), $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \Trigger $oTrigger */
/** @var \TriggerOnObjectCreate $oTrigger */
try
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch(Exception $e)
{
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
@@ -2953,8 +3037,6 @@ abstract class DBObject implements iDisplay
* Persist an object to the DB, for the first time
*
* @api
* @see DBWrite
*
* @return int|null inserted object key
*
* @throws \ArchivedObjectException
@@ -2964,10 +3046,12 @@ abstract class DBObject implements iDisplay
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*
* @see DBWrite
*/
public function DBInsert()
{
$this->DBInsertNoReload();
$this->DBInsertNoReload();
if (MetaModel::DBIsReadOnly())
{
@@ -3066,13 +3150,13 @@ abstract class DBObject implements iDisplay
* Update an object in DB
*
* @api
* @see DBObject::DBWrite()
*
* @return int object key
*
* @throws \CoreException
* @throws \CoreCannotSaveObjectException if CheckToWrite() returns issues
* @throws \Exception
*
* @see DBObject::DBWrite()
*/
public function DBUpdate()
{
@@ -3080,6 +3164,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead");
}
// Protect against reentrance (e.g. cascading the update of ticket logs)
static $aUpdateReentrance = array();
$sKey = get_class($this).'::'.$this->GetKey();
@@ -3093,8 +3178,11 @@ abstract class DBObject implements iDisplay
try
{
$oKPI = new ExecutionKPI();
$this->DoComputeValues();
// Stop watches
$oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
// Stop watches
$sState = $this->GetState();
if ($sState != '')
{
@@ -3113,7 +3201,9 @@ abstract class DBObject implements iDisplay
}
}
}
$this->OnUpdate();
$oKPI = new ExecutionKPI();
$this->OnUpdate();
$oKPI->ComputeStatsForExtension($this, 'OnUpdate');
$aChanges = $this->ListChanges();
if (count($aChanges) == 0)
@@ -3125,7 +3215,7 @@ abstract class DBObject implements iDisplay
}
// Ultimate check - ensure DB integrity
list($bRes, $aIssues) = $this->CheckToWrite();
[$bRes, $aIssues] = $this->CheckToWrite();
if (!$bRes)
{
throw new CoreCannotSaveObjectException(array(
@@ -3150,6 +3240,7 @@ abstract class DBObject implements iDisplay
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
@@ -3324,7 +3415,9 @@ abstract class DBObject implements iDisplay
try
{
$this->AfterUpdate();
$oKPI = new ExecutionKPI();
$this->AfterUpdate();
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
// Reload to get the external attributes
if ($bHasANewExternalKeyValue)
@@ -3403,13 +3496,18 @@ abstract class DBObject implements iDisplay
/**
* Make the current changes persistent - clever wrapper for Insert or Update
*
* @api
*
* @api
*
* @return int
*
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
*
* @throws ArchivedObjectException
* @throws CoreCannotSaveObjectException
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws CoreWarning
* @throws MySQLException
* @throws OQLException
*/
public function DBWrite()
{
@@ -3470,13 +3568,13 @@ abstract class DBObject implements iDisplay
$aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \Trigger $oTrigger */
/** @var \TriggerOnObjectDelete $oTrigger */
try
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch(Exception $e)
{
catch(Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
@@ -3891,6 +3989,7 @@ abstract class DBObject implements iDisplay
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
@@ -3902,6 +4001,7 @@ abstract class DBObject implements iDisplay
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}

View File

@@ -763,7 +763,10 @@ class DBObjectSet implements iDBObjectSetIterator
try
{
$oKPI = new ExecutionKPI();
$this->m_oSQLResult = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, $this->GetRealSortOrder(), $this->m_iLimitCount, $this->m_iLimitStart, false);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
} catch (MySQLException $e)
{
// 1116 = ER_TOO_MANY_TABLES
@@ -843,8 +846,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
$resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), 0, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if (!$resQuery) return 0;
$aRow = CMDBSource::FetchArray($resQuery);
@@ -855,6 +861,42 @@ class DBObjectSet implements iDBObjectSetIterator
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
}
/**
* @param \DBSearch $oFilter
* @param array $aOrder
* @param int $iLimitCount
* @param int $iLimitStart
* @param bool $bCount
*
* @return string
*/
private function GetPseudoOQL($oFilter, $aOrder, $iLimitCount, $iLimitStart, $bCount)
{
$sOQL = '';
if ($bCount) {
$sOQL .= 'COUNT ';
}
$sOQL .= $oFilter->ToOQL();
if ($iLimitCount > 0) {
$sOQL .= ' LIMIT ';
if ($iLimitStart > 0) {
$sOQL .= "$iLimitStart, ";
}
$sOQL .= "$iLimitCount";
}
if (count($aOrder) > 0) {
$sOQL .= ' ORDER BY ';
$aOrderBy = [];
foreach ($aOrder as $sAttCode => $bAsc) {
$aOrderBy[] = $sAttCode.' '.($bAsc ? 'ASC' : 'DESC');
}
$sOQL .= implode(', ', $aOrderBy);
}
return $sOQL;
}
/**
* Check if the count exceeds a given limit
*
@@ -871,8 +913,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
@@ -883,7 +928,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$iCount = 0;
}
}
}
else
{
$iCount = $this->m_iNumTotalDBRows;
@@ -908,8 +953,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
@@ -920,7 +968,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$iCount = 0;
}
}
}
else
{
$iCount = $this->m_iNumTotalDBRows;

View File

@@ -754,14 +754,14 @@ abstract class DBSearch
* @see DBSearch::ToOQL()
*
* @param string $sQuery The OQL to convert to a DBSearch
* @param mixed[string] $aParams array of <mixed> params index by <string> name
* @param array $aParams array of <mixed> params index by <string> name
* @param ModelReflection|null $oMetaModel The MetaModel to use when checking the consistency of the OQL
*
* @return DBObjectSearch|DBUnionSearch
*
* @throws OQLException
*/
static public function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
public static function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
{
if (empty($sQuery))
{

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -18,7 +18,7 @@
/**
* Class Dict
* Management of localizable strings
* Management of localizable strings
*
* @copyright Copyright (C) 2010-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
@@ -144,33 +144,50 @@ class Dict
* @return string
*/
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
$aInfo = self::GetLabelAndLangCode($sStringCode, $sDefault, $bUserLanguageOnly);
return $aInfo['label'];
}
/**
* Returns a localised string from the dictonary with its associated lang code
*
* @param string $sStringCode The code identifying the dictionary entry
* @param string $sDefault Default value if there is no match in the dictionary
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
*
* @return array{
* lang: string, label: string
* } with localized label string and used lang code
*/
private static function GetLabelAndLangCode($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
// Attempt to find the string in the user language
//
$sLangCode = self::GetUserLanguage();
self::InitLangIfNeeded($sLangCode);
if (!array_key_exists($sLangCode, self::$m_aData))
if (! array_key_exists($sLangCode, self::$m_aData))
{
IssueLog::Warning("Cannot find $sLangCode in dictionnaries. default labels displayed");
IssueLog::Warning("Cannot find $sLangCode in all registered dictionaries.");
// It may happen, when something happens before the dictionaries get loaded
return $sStringCode;
return [ 'label' => $sStringCode, 'lang' => $sLangCode ];
}
$aCurrentDictionary = self::$m_aData[$sLangCode];
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
{
return $aCurrentDictionary[$sStringCode];
return [ 'label' => $aCurrentDictionary[$sStringCode], 'lang' => $sLangCode ];
}
if (!$bUserLanguageOnly)
{
// Attempt to find the string in the default language
//
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
{
return $aDefaultDictionary[$sStringCode];
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => self::$m_sDefaultLanguage ];
}
// Attempt to find the string in english
//
@@ -179,17 +196,17 @@ class Dict
$aDefaultDictionary = self::$m_aData['EN US'];
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
{
return $aDefaultDictionary[$sStringCode];
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => 'EN US' ];
}
}
// Could not find the string...
//
if (is_null($sDefault))
{
return $sStringCode;
return [ 'label' => $sStringCode, 'lang' => null ];
}
return $sDefault;
return [ 'label' => $sDefault, 'lang' => null ];
}
@@ -205,19 +222,25 @@ class Dict
*/
public static function Format($sFormatCode /*, ... arguments ....*/)
{
$sLocalizedFormat = self::S($sFormatCode);
['label' => $sLocalizedFormat, 'lang' => $sLangCode] = self::GetLabelAndLangCode($sFormatCode);
$aArguments = func_get_args();
array_shift($aArguments);
if ($sLocalizedFormat == $sFormatCode)
{
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
return $sFormatCode.' - '.implode(', ', $aArguments);
}
return vsprintf($sLocalizedFormat, $aArguments);
try{
return vsprintf($sLocalizedFormat, $aArguments);
} catch(\Throwable $e){
\IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]);
return $sFormatCode.' - '.implode(', ', $aArguments);
}
}
/**
* Initialize a the entries for a given language (replaces the former Add() method)
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
@@ -227,7 +250,7 @@ class Dict
{
self::$m_aData[$sLanguageCode] = $aEntries;
}
/**
* Set the list of available languages
* @param hash $aLanguagesList
@@ -288,7 +311,7 @@ class Dict
{
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
require_once($sDictFile);
if (self::GetApcService()->function_exists('apc_store')
&& (self::$m_sApplicationPrefix !== null))
{
@@ -298,7 +321,7 @@ class Dict
}
return $bResult;
}
/**
* Enable caching (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
@@ -342,14 +365,14 @@ class Dict
}
}
}
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
{
$aMissing = array(); // Strings missing for the target language
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
$aNotTranslated = array(); // Strings having the same value in both dictionaries
$aOK = array(); // Strings having different values in both dictionaries
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
@@ -357,7 +380,7 @@ class Dict
$aMissing[$sStringCode] = $sValue;
}
}
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
@@ -380,7 +403,7 @@ class Dict
}
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
}
public static function Dump()
{
MyHelpers::var_dump_html(self::$m_aData);
@@ -403,7 +426,7 @@ class Dict
// No need to actually load the strings since it's only used to know the list of languages
// at setup time !!
}
/**
* Export all the dictionary entries - of the given language - whose code matches the given prefix
* missing entries in the current language will be replaced by entries in the default language
@@ -416,7 +439,7 @@ class Dict
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aEntries = array();
$iLength = strlen($sStartingWith);
// First prefill the array with entries from the default language
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
{
@@ -425,7 +448,7 @@ class Dict
$aEntries[$sCode] = $sEntry;
}
}
// Now put (overwrite) the entries for the user language
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
{

View File

@@ -23,6 +23,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\ExportHelper;
require_once(APPROOT.'application/xlsxwriter.class.php');
class ExcelBulkExport extends TabularBulkExport
@@ -89,6 +91,7 @@ class ExcelBulkExport extends TabularBulkExport
case 'xlsx_options':
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:XLSXOptions').'</legend>');
$oP->add(ExportHelper::GetAlertForExcelMaliciousInjection());
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';

View File

@@ -49,10 +49,9 @@ abstract class HTMLSanitizer
$sSanitizerClass = 'HTMLDOMSanitizer';
} else if (false === is_subclass_of($sSanitizerClass, HTMLSanitizer::class)) {
if ($sConfigKey === 'html_sanitizer') {
IssueLog::Warning('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of HTMLSanitizer. Will use HTMLDOMSanitizer as the default sanitizer.');
IssueLog::Warning('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of '.HTMLSanitizer::class.'. Will use HTMLDOMSanitizer as the default sanitizer.');
$sSanitizerClass = 'HTMLDOMSanitizer';
}
if ($sConfigKey === 'svg_sanitizer') {
} else {
IssueLog::Error('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of '.HTMLSanitizer::class.' ! Won\'t sanitize string.');
return $sHTML;

View File

@@ -15,6 +15,8 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Core\Kpi\KpiLogData;
use Combodo\iTop\Service\Module\ModuleService;
/**
@@ -30,6 +32,8 @@ class ExecutionKPI
static protected $m_bEnabled_Memory = false;
static protected $m_bBlameCaller = false;
static protected $m_sAllowedUser = '*';
static protected $m_bGenerateLegacyReport = true;
static protected $m_fSlowQueries = 0;
static protected $m_aStats = array(); // Recurrent operations
static protected $m_aExecData = array(); // One shot operations
@@ -77,14 +81,39 @@ class ExecutionKPI
return false;
}
static public function SetGenerateLegacyReport($bReportExtensionsOnly)
{
self::$m_bGenerateLegacyReport = $bReportExtensionsOnly;
}
static public function SetSlowQueries($fSlowQueries)
{
self::$m_fSlowQueries = $fSlowQueries;
}
static public function GetDescription()
{
$aFeatures = array();
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
$sFeatures = implode(', ', $aFeatures);
$sFeatures = 'Measures: '.implode(', ', $aFeatures);
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
return "KPI logging is active for $sFor. Measures: $sFeatures";
$sSlowQueries = '';
if (self::$m_fSlowQueries > 0) {
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
}
$aExtensions = [];
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$aExtensions[] = ModuleService::GetInstance()->GetModuleNameFromObject($oExtensionInstance);
}
$sExtensions = '';
if (count($aExtensions) > 0) {
$sExtensions = '. KPI Extensions: ['.implode(', ', $aExtensions).']';
}
return "KPI logging is active for $sFor. $sFeatures$sSlowQueries$sExtensions";
}
static public function ReportStats()
@@ -92,7 +121,28 @@ class ExecutionKPI
if (!self::IsEnabled()) return;
global $fItopStarted;
global $iItopInitialMemory;
$sExecId = microtime(); // id to differentiate the hrefs!
$sRequest = $_SERVER['REQUEST_URI'].' ('.$_SERVER['REQUEST_METHOD'].')';
if (isset($_POST['operation'])) {
$sRequest .= ' operation: '.$_POST['operation'];
}
$fStop = MyHelpers::getmicrotime();
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
$iCurrentMemory = self::memory_get_usage();
$iPeakMemory = self::memory_get_peak_usage();
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$oKPILogData = new KpiLogData(KpiLogData::TYPE_REQUEST, 'Page', $sRequest, $fItopStarted, $fStop, '', $iItopInitialMemory, $iCurrentMemory, $iPeakMemory);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
if (!self::$m_bGenerateLegacyReport) {
return;
}
$aBeginTimes = array();
foreach (self::$m_aExecData as $aOpStats)
@@ -105,7 +155,7 @@ class ExecutionKPI
self::Report("<hr/>");
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>");
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
self::Report("<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>");
self::Report("<div>");
@@ -200,8 +250,6 @@ class ExecutionKPI
self::Report("<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>");
$fSlowQueries = MetaModel::GetConfig()->Get('log_kpi_slow_queries');
// Report operation details
foreach (self::$m_aStats as $sOperation => $aOpStats)
{
@@ -245,7 +293,7 @@ class ExecutionKPI
$sTotalInter = round($fTotalInter, 3);
$sMinInter = round($fMinInter, 3);
$sMaxInter = round($fMaxInter, 3);
if (($fTotalInter >= $fSlowQueries))
if (($fTotalInter >= self::$m_fSlowQueries))
{
if ($bDisplayHeader)
{
@@ -271,11 +319,19 @@ class ExecutionKPI
self::Report('<a name="end-'.md5($sExecId).'">&nbsp;</a>');
}
public static function InitStats()
{
// Invoke extensions to initialize the KPI statistics
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$oExtensionInstance->InitStats();
}
}
public function __construct()
{
$this->ResetCounters();
}
}
// Get the duration since startup, and reset the counter for the next measure
//
@@ -283,8 +339,14 @@ class ExecutionKPI
{
global $fItopStarted;
if (!self::IsEnabled()) {
return;
}
$aNewEntry = null;
$fStarted = $this->m_fStarted;
$fStopped = $this->m_fStarted;
if (self::$m_bEnabled_Duration)
{
$fStopped = MyHelpers::getmicrotime();
@@ -297,6 +359,9 @@ class ExecutionKPI
$this->m_fStarted = $fStopped;
}
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
$iCurrentMemory = 0;
$iPeakMemory = 0;
if (self::$m_bEnabled_Memory)
{
$iCurrentMemory = self::memory_get_usage();
@@ -306,41 +371,102 @@ class ExecutionKPI
}
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
$aNewEntry['mem_end'] = $iCurrentMemory;
if (function_exists('memory_get_peak_usage'))
{
$aNewEntry['mem_peak'] = memory_get_peak_usage();
}
$iPeakMemory = self::memory_get_peak_usage();
$aNewEntry['mem_peak'] = $iPeakMemory;
// Reset for the next operation (if the object is recycled)
$this->m_iInitialMemory = $iCurrentMemory;
}
if (!is_null($aNewEntry))
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) {
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance)
{
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_REPORT,
'Step',
$sOperationDesc,
$fStarted,
$fStopped,
$sExtension,
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
if (!is_null($aNewEntry) && self::$m_bGenerateLegacyReport)
{
self::$m_aExecData[] = $aNewEntry;
}
$this->ResetCounters();
}
public function ComputeStatsForExtension($object, $sMethod)
{
if (!self::IsEnabled()) {
return;
}
$sSignature = ModuleService::GetInstance()->GetModuleMethodSignature($object, $sMethod);
if (utils::StartsWith($sSignature, '[')) {
$this->ComputeStats('Extension', $sSignature);
}
}
public function ComputeStats($sOperation, $sArguments)
{
if (!self::IsEnabled()) {
return;
}
if (self::$m_bEnabled_Duration)
{
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
if (self::$m_bBlameCaller)
{
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration,
'callers' => MyHelpers::get_callstack(1),
);
}
else
{
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration
);
}
}
$aCallstack = [];
if (self::$m_bGenerateLegacyReport) {
if (self::$m_bBlameCaller) {
$aCallstack = MyHelpers::get_callstack(1);
self::$m_aStats[$sOperation][$sArguments][] = [
'time' => $fDuration,
'callers' => $aCallstack,
];
} else {
self::$m_aStats[$sOperation][$sArguments][] = [
'time' => $fDuration
];
}
}
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
$iCurrentMemory = 0;
$iPeakMemory = 0;
if (self::$m_bEnabled_Memory)
{
$iCurrentMemory = self::memory_get_usage();
$iPeakMemory = self::memory_get_peak_usage();
}
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_STATS,
$sOperation,
$sArguments,
$this->m_fStarted,
$fStopped,
$sExtension,
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory,
$aCallstack);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
}
protected function ResetCounters()
@@ -370,35 +496,7 @@ class ExecutionKPI
static protected function memory_get_usage()
{
if (function_exists('memory_get_usage'))
{
return memory_get_usage(true);
}
// Copied from the PHP manual
//
//If its Windows
//Tested on Win XP Pro SP2. Should work on Win 2003 Server too
//Doesn't work for 2000
//If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
if (substr(PHP_OS,0,3) == 'WIN')
{
$output = array();
exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
}
else
{
//We now assume the OS is UNIX
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
//This should work on most UNIX systems
$pid = getmypid();
exec("ps -eo%mem,rss,pid | grep $pid", $output);
$output = explode(" ", $output[0]);
//rss is given in 1024 byte units
return $output[1] * 1024;
}
return memory_get_usage(true);
}
static public function memory_get_peak_usage($bRealUsage = false)

View File

@@ -549,6 +549,12 @@ class LogChannels
const DEADLOCK = 'DeadLock';
/**
* @var string
* @since 2.7.9
*/
const EXPORT = 'export';
const INLINE_IMAGE = 'InlineImage';
/**
@@ -735,7 +741,9 @@ class ToolsLog extends LogAPI
/**
* @see \CMDBSource::LogDeadLock()
* @since 2.7.1
* @since 2.7.1 PR #139
*
* @link https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks.html
*/
class DeadLockLog extends LogAPI
{
@@ -755,14 +763,15 @@ class DeadLockLog extends LogAPI
parent::Enable($sTargetFile);
}
/** @noinspection PhpUnreachableStatementInspection */
private static function GetChannelFromMysqlErrorNo($iMysqlErrorNo)
{
switch ($iMysqlErrorNo)
{
case 1205:
case CMDBSource::MYSQL_ERRNO_WAIT_TIMEOUT:
return self::CHANNEL_WAIT_TIMEOUT;
break;
case 1213:
case CMDBSource::MYSQL_ERRNO_DEADLOCK:
return self::CHANNEL_DEADLOCK_FOUND;
break;
default:

View File

@@ -1213,8 +1213,10 @@ abstract class MetaModel
*
* @return AttributeDefinition[]
* @throws \CoreException
*
* @see GetAttributesList for attcode list
*/
final static public function ListAttributeDefs($sClass)
final public static function ListAttributeDefs($sClass)
{
self::_check_subclass($sClass);
return self::$m_aAttribDefs[$sClass];
@@ -1223,8 +1225,10 @@ abstract class MetaModel
/**
* @param string $sClass
*
* @return array
* @return string[] list of attcodes
* @throws \CoreException
*
* @see ListAttributeDefs to get AttributeDefinition array instead
*/
final public static function GetAttributesList($sClass)
{
@@ -2778,7 +2782,23 @@ abstract class MetaModel
// Build the list of available extensions
//
$aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension');
$aInterfaces = [
'iLoginFSMExtension',
'iLogoutExtension',
'iLoginUIExtension',
'iPreferencesExtension',
'iApplicationUIExtension',
'iApplicationObjectExtension',
'iPopupMenuExtension',
'iPageUIExtension',
'iPortalUIExtension',
'iQueryModifier',
'iOnClassInitialization',
'iModuleExtension',
'iKPILoggerExtension',
'ModuleHandlerApiInterface',
'iNewsroomProvider',
];
foreach($aInterfaces as $sInterface)
{
self::$m_aExtensionClasses[$sInterface] = array();
@@ -6348,7 +6368,9 @@ abstract class MetaModel
ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration'));
ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory'));
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
ExecutionKPI::SetGenerateLegacyReport(self::$m_oConfig->Get('log_kpi_generate_legacy_report'));
ExecutionKPI::SetSlowQueries(self::$m_oConfig->Get('log_kpi_slow_queries'));
self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write');
self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys');
@@ -6485,6 +6507,7 @@ abstract class MetaModel
CMDBSource::InitFromConfig(self::$m_oConfig);
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
ExecutionKPI::InitStats();
}
/**
@@ -6704,7 +6727,13 @@ abstract class MetaModel
if ($bMustBeFound && empty($aRow))
{
throw new CoreException("No result for the single row query: '$sSQL'");
$sNotFoundErrorMessage = "No result for the single row query";
IssueLog::Info($sNotFoundErrorMessage, LogChannels::CMDB_SOURCE, [
'class' => $sClass,
'key' => $iKey,
'sql_query' => $sSQL,
]);
throw new CoreException($sNotFoundErrorMessage);
}
return $aRow;
@@ -6784,28 +6813,21 @@ abstract class MetaModel
* $bMustBeFound=false)
* @throws CoreException if no result found and $bMustBeFound=true
* @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true
* @throws \Exception
*
*/
public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
{
$oObject = self::GetObjectWithArchive($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties);
if (empty($oObject))
{
if (empty($oObject)) {
return null;
}
if (!utils::IsArchiveMode() && $oObject->IsArchived())
{
if ($bMustBeFound)
{
if (!utils::IsArchiveMode() && $oObject->IsArchived()) {
if ($bMustBeFound) {
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
}
else
{
return null;
}
return null;
}
return $oObject;

View File

@@ -22,7 +22,9 @@
* A class to serialize the execution of some code sections
* Emulates the API of PECL Mutex class
* Relies on MySQL locks because the API sem_get is not always present in the
* installed PHP.
* installed PHP.
*
* @link https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html MySQL locking functions documentation
*
* @copyright Copyright (C) 2013-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
@@ -257,7 +259,7 @@ class iTopMutex
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
if (!$this->hDBLink) {
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
}
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,

View File

@@ -25,6 +25,19 @@
class PDFBulkExport extends HTMLBulkExport
{
/**
* @var string For sample purposes
* @internal
* @since 2.7.8
*/
const ENUM_OUTPUT_TYPE_SAMPLE = 'sample';
/**
* @var string For the real export
* @internal
* @since 2.7.8
*/
const ENUM_OUTPUT_TYPE_REAL = 'real';
public function DisplayUsage(Page $oP)
{
$oP->p(" * pdf format options:");
@@ -190,46 +203,46 @@ EOF
return $sPDF;
}
/**
* @inheritDoc
* @since 2.7.8
*/
protected function GetSampleData($oObj, $sAttCode)
{
if ($sAttCode !== 'id')
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
// As sample data will be displayed in the web browser, AttributeImage needs to be rendered with a regular HTML format, meaning its "src" looking like "data:image/png;base64,iVBORw0KGgoAAAANSUh..."
// Whereas for the PDF generation it needs to be rendered with a TCPPDF-compatible format, meaning its "src" looking like "@iVBORw0KGgoAAAANSUh..."
if ($oAttDef instanceof AttributeImage) {
return $this->GetAttributeImageValue($oObj, $sAttCode, static::ENUM_OUTPUT_TYPE_SAMPLE);
}
}
return parent::GetSampleData($oObj, $sAttCode);
}
/**
* @param \DBObject $oObj
* @param string $sAttCode
*
* @return int|string
* @throws \Exception
*/
protected function GetValue($oObj, $sAttCode)
{
switch($sAttCode)
{
switch ($sAttCode) {
case 'id':
$sRet = parent::GetValue($oObj, $sAttCode);
break;
default:
$value = $oObj->Get($sAttCode);
if ($value instanceof ormDocument)
{
if ($value instanceof ormDocument) {
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if ($oAttDef instanceof AttributeImage)
{
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
//
$iDefaultMaxWidthPx = 48;
$iDefaultMaxHeightPx = 48;
if ($value->IsEmpty())
{
$iNewWidth = $iDefaultMaxWidthPx;
$iNewHeight = $iDefaultMaxHeightPx;
$sUrl = $oAttDef->Get('default_image');
}
else
{
list($iWidth, $iHeight) = utils::GetImageSize($value->GetData());
$iMaxWidthPx = min($iDefaultMaxWidthPx, $oAttDef->Get('display_max_width'));
$iMaxHeightPx = min($iDefaultMaxHeightPx, $oAttDef->Get('display_max_height'));
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
}
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px">' : '';
$sRet = '<div class="view-image">'.$sRet.'</div>';
$sRet = $this->GetAttributeImageValue($oObj, $sAttCode, static::ENUM_OUTPUT_TYPE_REAL);
}
else
{
@@ -258,4 +271,74 @@ EOF
{
return 'pdf';
}
/**
* @param \DBObject $oObj
* @param string $sAttCode
* @param string $sOutputType {@see \PDFBulkExport::ENUM_OUTPUT_TYPE_SAMPLE}, {@see \PDFBulkExport::ENUM_OUTPUT_TYPE_REAL}
*
* @return string Rendered value of $oAttDef / $oValue according to the desired $sOutputType
* @throws \ArchivedObjectException
* @throws \CoreException
*
* @since 2.7.8 N°2244 method creation
* @since 2.7.9 N°5588 signature change to get the object so that we can log all the needed information
*/
protected function GetAttributeImageValue(DBObject $oObj, string $sAttCode, string $sOutputType)
{
$oValue = $oObj->Get($sAttCode);
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
//
$iDefaultMaxWidthPx = 48;
$iDefaultMaxHeightPx = 48;
if ($oValue->IsEmpty()) {
$iNewWidth = $iDefaultMaxWidthPx;
$iNewHeight = $iDefaultMaxHeightPx;
$sUrl = $oAttDef->Get('default_image');
} else {
$iMaxWidthPx = min($iDefaultMaxWidthPx, $oAttDef->Get('display_max_width'));
$iMaxHeightPx = min($iDefaultMaxHeightPx, $oAttDef->Get('display_max_height'));
list($iWidth, $iHeight) = utils::GetImageSize($oValue->GetData());
if ((is_null($iWidth)) || (is_null($iHeight)) || ($iWidth === 0) || ($iHeight === 0)) {
// Avoid division by zero exception (SVGs, corrupted images, ...)
$iNewWidth = $iDefaultMaxWidthPx;
$iNewHeight = $iDefaultMaxHeightPx;
$sAttCode = $oAttDef->GetCode();
IssueLog::Warning('AttributeImage: Cannot read image size', LogChannels::EXPORT, [
'ObjClass' => get_class($oObj),
'ObjKey' => $oObj->GetKey(),
'ObjFriendlyName' => $oObj->GetName(),
'AttCode' => $sAttCode,
]);
} else {
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
}
$sValueAsBase64 = base64_encode($oValue->GetData());
switch ($sOutputType) {
case static::ENUM_OUTPUT_TYPE_SAMPLE:
$sUrl = 'data:'.$oValue->GetMimeType().';base64,'.$sValueAsBase64;
break;
case static::ENUM_OUTPUT_TYPE_REAL:
default:
// TCPDF requires base64-encoded images to be rendered without the usual "data:<MIMETYPE>;base64" header but with an "@"
// @link https://tcpdf.org/examples/example_009/
$sUrl = '@'.$sValueAsBase64;
break;
}
}
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px; vertical-align: middle; text-align:center;">' : '';
$sRet = '<div class="view-image">'.$sRet.'</div>';
return $sRet;
}
}

View File

@@ -129,7 +129,7 @@ class ObjectResult
/**
* REST response for services managing objects. Derive this structure to add information and/or constants
*
* @package Extensibility
* @package RESTExtensibilityAPI
* @package REST Services
* @api
*/
@@ -206,7 +206,7 @@ class RestResultWithRelations extends RestResultWithObjects
/**
* Deletion result codes for a target object (either deleted or updated)
*
* @package Extensibility
* @package RESTExtensibilityAPI
* @api
* @since 2.0.1
*/

View File

@@ -126,7 +126,9 @@ abstract class Trigger extends cmdbAbstractObject
$oAction = MetaModel::GetObject('Action', $iActionId);
if ($oAction->IsActive())
{
$oKPI = new ExecutionKPI();
$oAction->DoExecute($this, $aContextArgs);
$oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
}
}
}
@@ -259,21 +261,48 @@ abstract class TriggerOnObject extends Trigger
public function IsTargetObject($iObjectId, $aChanges = array())
{
$sFilter = trim($this->Get('filter'));
if (strlen($sFilter) > 0)
{
if (strlen($sFilter) > 0) {
$oSearch = DBObjectSearch::FromOQL($sFilter);
$oSearch->AddCondition('id', $iObjectId, '=');
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch);
$bRet = ($oSet->Count() > 0);
}
else
{
} else {
$bRet = true;
}
return $bRet;
}
/**
* @param Exception $oException
* @param \DBObject $oObject
*
* @return void
*
* @uses \IssueLog::Error()
*
* @since 2.7.9 3.0.3 3.1.0 N°5893
*/
public function LogException($oException, $oObject)
{
$sObjectKey = $oObject->GetKey(); // if object wasn't persisted yet, then we'll have a negative value
$aContext = [
'exception.class' => get_class($oException),
'exception.message' => $oException->getMessage(),
'trigger.class' => get_class($this),
'trigger.id' => $this->GetKey(),
'trigger.friendlyname' => $this->GetRawName(),
'object.class' => get_class($oObject),
'object.id' => $sObjectKey,
'object.friendlyname' => $oObject->GetRawName(),
'current_user' => UserRights::GetUser(),
'exception.stack' => $oException->getTraceAsString(),
];
IssueLog::Error('A trigger did throw an exception', null, $aContext);
}
}
/**

View File

@@ -817,6 +817,9 @@ class UserRights
}
}
/**
* @return string connected {@see User} login field value, otherwise empty string
*/
public static function GetUser()
{
if (is_null(self::$m_oUser))
@@ -829,7 +832,9 @@ class UserRights
}
}
/** User */
/**
* @return User|null
*/
public static function GetUserObject()
{
if (is_null(self::$m_oUser))
@@ -1029,7 +1034,7 @@ class UserRights
/**
* @param string $sClass
* @param int $iActionCode
* @param int $iActionCode see UR_ACTION_* constants
* @param DBObjectSet $oInstanceSet
* @param User $oUser
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO|UR_ALLOWED_DEPENDS)

View File

@@ -17,7 +17,7 @@
*/
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.7.8";
$version: "v2.7.10";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors

View File

@@ -1,5 +1,13 @@
{
"require" : {
"apereo/phpcas" : "~1.3"
}
"config" : {
"classmap-authoritative" : true
},
"autoload" : {
"psr-4" : {
"Combodo\\iTop\\Cas\\" : "src"
}
},
"require" : {
"apereo/phpcas" : "~1.6.0"
}
}

View File

@@ -4,28 +4,32 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4db4df78154f0de344ba35a27fe766b7",
"content-hash": "46afbbe7e92c2ccfe403f366ef1877e5",
"packages": [
{
"name": "apereo/phpcas",
"version": "1.3.7",
"version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/apereo/phpCAS.git",
"reference": "b5b29102c3a42f570c4a3e852f3cf67cae6d6082"
"reference": "f817c72a961484afef95ac64a9257c8e31f063b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/apereo/phpCAS/zipball/b5b29102c3a42f570c4a3e852f3cf67cae6d6082",
"reference": "b5b29102c3a42f570c4a3e852f3cf67cae6d6082",
"url": "https://api.github.com/repos/apereo/phpCAS/zipball/f817c72a961484afef95ac64a9257c8e31f063b9",
"reference": "f817c72a961484afef95ac64a9257c8e31f063b9",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.4.0"
"ext-dom": "*",
"php": ">=7.1.0",
"psr/log": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"phpunit/phpunit": "~3.7.10"
"monolog/monolog": "^1.0.0 || ^2.0.0",
"phpstan/phpstan": "^1.5",
"phpunit/phpunit": ">=7.5"
},
"type": "library",
"extra": {
@@ -45,11 +49,16 @@
"authors": [
{
"name": "Joachim Fritschi",
"homepage": "https://wiki.jasig.org/display/~fritschi"
"email": "jfritschi@freenet.de",
"homepage": "https://github.com/jfritschi"
},
{
"name": "Adam Franco",
"homepage": "https://wiki.jasig.org/display/~adamfranco"
"homepage": "https://github.com/adamfranco"
},
{
"name": "Henry Pan",
"homepage": "https://github.com/phy25"
}
],
"description": "Provides a simple API for authenticating users against a CAS server",
@@ -59,7 +68,61 @@
"cas",
"jasig"
],
"time": "2019-04-22T19:48:16+00:00"
"support": {
"issues": "https://github.com/apereo/phpCAS/issues",
"source": "https://github.com/apereo/phpCAS/tree/1.6.0"
},
"time": "2022-10-31T20:39:27+00:00"
},
{
"name": "psr/log",
"version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.4"
},
"time": "2021-05-03T11:20:27+00:00"
}
],
"packages-dev": [],
@@ -69,5 +132,6 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
"platform-dev": [],
"plugin-api-version": "2.1.0"
}

View File

@@ -1,4 +0,0 @@
<?php
require_once __DIR__.'/vendor/autoload.php';
require_once __DIR__.'/src/Config.php';
require_once __DIR__.'/src/CASLoginExtension.php';

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-cas/2.7.8',
'authent-cas/2.7.10',
array(
// Identification
//
@@ -24,7 +24,8 @@ SetupWebPage::AddModule(
//
'datamodel' => array(
'model.authent-cas.php',
'main.php'
'vendor/autoload.php',
'src/CASLoginExtension.php',
),
'webservice' => array(
@@ -50,6 +51,7 @@ SetupWebPage::AddModule(
'cas_port' => '',
'cas_context' => '',
'cas_version' => '',
'service_base_url' => '',
),
)
);

View File

@@ -0,0 +1,17 @@
<?php
/**
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Cas;
use LogAPI;
class CASLog extends LogAPI
{
const CHANNEL_DEFAULT = 'CASLog';
protected static $m_oFileLog = null;
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Cas;
use IssueLog;
use LogAPI;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class CASLogger implements LoggerInterface
{
public function __construct($sDebugFile)
{
CASLog::Enable($sDebugFile);
}
const LEVEL_COMPAT = [
LogLevel::EMERGENCY => LogAPI::LEVEL_ERROR,
LogLevel::ALERT => LogAPI::LEVEL_ERROR,
LogLevel::CRITICAL => LogAPI::LEVEL_ERROR,
LogLevel::ERROR => LogAPI::LEVEL_ERROR,
LogLevel::WARNING => LogAPI::LEVEL_WARNING,
LogLevel::NOTICE => LogAPI::LEVEL_INFO,
LogLevel::INFO => LogAPI::LEVEL_INFO,
LogLevel::DEBUG => LogAPI::LEVEL_DEBUG,
];
public function emergency($message, array $context = array())
{
CASLog::Error('EMERGENCY: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('EMERGENCY: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function alert($message, array $context = array())
{
CASLog::Error('ALERT: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('ALERT: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function critical($message, array $context = array())
{
CASLog::Error('CRITICAL: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('CRITICAL: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function error($message, array $context = array())
{
CASLog::Error('ERROR: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('ERROR: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function warning($message, array $context = array())
{
CASLog::Warning('WARNING: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function notice($message, array $context = array())
{
CASLog::Info('NOTICE: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function info($message, array $context = array())
{
CASLog::Info('INFO: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function debug($message, array $context = array())
{
CASLog::Debug('DEBUG: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function log($level, $message, array $context = array())
{
$sLevel = self::LEVEL_COMPAT[$level] ?? LogAPI::LEVEL_ERROR;
CASLog::Log($sLevel, strtoupper($level).": $message", CASLog::CHANNEL_DEFAULT, $context);
}
}

View File

@@ -47,6 +47,11 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnReadCredentials(&$iErrorCode)
{
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
// Not allowed if not already connected
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'cas'))
{
static::InitCASClient();
@@ -71,7 +76,8 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
return LoginWebPage::LOGIN_FSM_ERROR;
}
}
$_SESSION['login_mode'] = 'cas';
$_SESSION['login_mode'] = 'cas';
phpCAS::forceAuthentication(); // Redirect to CAS and exit
}
}
@@ -80,7 +86,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'cas')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
{
if (!isset($_SESSION['auth_user']))
{
@@ -97,7 +103,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'cas')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
{
$sAuthUser = $_SESSION['auth_user'];
if (!LoginWebPage::CheckUser($sAuthUser))
@@ -112,9 +118,15 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'cas')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
{
unset($_SESSION['phpCAS']);
unset($_SESSION['phpCAS']);
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
// don't display the login page
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
if ($iErrorCode != LoginWebPage::EXIT_CODE_MISSINGLOGIN)
{
$oLoginWebPage = new LoginWebPage();
@@ -127,7 +139,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'cas')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'cas')
{
$_SESSION['can_logoff'] = true;
return LoginWebPage::CheckLoggedUser($iErrorCode);
@@ -154,15 +166,16 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
$bCASDebug = Config::Get('cas_debug');
if ($bCASDebug)
{
phpCAS::setDebug(APPROOT.'log/cas.log');
phpCAS::setLogger(new CASLogger(APPROOT.'log/cas.log'));
}
// Initialize phpCAS
$sCASVersion = Config::Get('cas_version');
$sCASHost = Config::Get('cas_host');
$iCASPort = Config::Get('cas_port');
$sCASContext = Config::Get('cas_context');
phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */);
$sServiceBaseURL = Config::Get('service_base_url', self::GetServiceBaseURL());
phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, $sServiceBaseURL, false /* session already started */);
$sCASCACertPath = Config::Get('cas_server_ca_cert_path');
if (empty($sCASCACertPath))
{
@@ -178,6 +191,38 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
}
}
private static function GetServiceBaseURL()
{
$protocol = $_SERVER['REQUEST_SCHEME'];
$protocol .= '://';
if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
// explode the host list separated by comma and use the first host
$hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
// see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default
return $protocol . $hosts[0];
} else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
$server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
} else {
if (empty($_SERVER['SERVER_NAME'])) {
$server_url = $_SERVER['HTTP_HOST'];
} else {
$server_url = $_SERVER['SERVER_NAME'];
}
}
if (!strpos($server_url, ':')) {
if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
$server_port = $_SERVER['SERVER_PORT'];
} else {
$ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']);
$server_port = $ports[0];
}
$server_url .= ':';
$server_url .= $server_port;
}
return $protocol . $server_url;
}
private function DoUserProvisioning($sLogin)
{
$bCASUserSynchro = Config::Get('cas_user_synchro');

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS.php
* @category Authentication
@@ -27,4 +27,6 @@
* @link https://wiki.jasig.org/display/CASC/phpCAS
*/
require_once dirname(__FILE__).'/source/CAS.php';
require_once __DIR__.'/source/CAS.php';
trigger_error('Including CAS.php is deprecated. Install phpCAS using composer instead.', E_USER_DEPRECATED);

View File

@@ -6,22 +6,21 @@ users via a Central Authentication Service (CAS) server.
Please see the wiki website for more information:
https://wiki.jasig.org/display/CASC/phpCAS
https://apereo.github.io/phpCAS/
Api documentation can be found here:
https://apereo.github.io/phpCAS/
https://apereo.github.io/phpCAS/api/
[![Build Status](https://travis-ci.org/apereo/phpCAS.png)](https://travis-ci.org/apereo/phpCAS)
[![Test](https://github.com/apereo/phpCAS/actions/workflows/test.yml/badge.svg)](https://github.com/apereo/phpCAS/actions/workflows/test.yml)
LICENSE
-------
Copyright 2007-2015, JA-SIG, Inc.
This project includes software developed by Jasig.
http://www.jasig.org/
Copyright 2007-2020, Apereo Foundation.
This project includes software developed by Apereo Foundation.
http://www.apereo.org/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this software except in compliance with the License.

View File

@@ -1,29 +1,55 @@
{
"name": "apereo/phpcas",
"description": "Provides a simple API for authenticating users against a CAS server",
"keywords": ["cas", "jasig", "apereo"],
"homepage": "https://wiki.jasig.org/display/CASC/phpCAS",
"type": "library",
"license": "Apache-2.0",
"authors": [
{"name": "Joachim Fritschi", "homepage": "https://wiki.jasig.org/display/~fritschi"},
{"name": "Adam Franco", "homepage": "https://wiki.jasig.org/display/~adamfranco"}
],
"require": {
"php": ">=5.4.0",
"ext-curl": "*"
"name" : "apereo/phpcas",
"description" : "Provides a simple API for authenticating users against a CAS server",
"keywords" : [
"cas",
"jasig",
"apereo"
],
"homepage" : "https://wiki.jasig.org/display/CASC/phpCAS",
"type" : "library",
"license" : "Apache-2.0",
"authors" : [{
"name" : "Joachim Fritschi",
"homepage" : "https://github.com/jfritschi",
"email" : "jfritschi@freenet.de"
}, {
"name" : "Adam Franco",
"homepage" : "https://github.com/adamfranco"
}, {
"name" : "Henry Pan",
"homepage" : "https://github.com/phy25"
}
],
"require" : {
"php" : ">=7.1.0",
"ext-curl" : "*",
"ext-dom" : "*",
"psr/log" : "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"phpunit/phpunit": "~3.7.10"
},
"autoload": {
"classmap": [
"source/"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
}
"require-dev" : {
"monolog/monolog" : "^1.0.0 || ^2.0.0",
"phpunit/phpunit" : ">=7.5",
"phpstan/phpstan" : "^1.5"
},
"autoload" : {
"classmap" : [
"source/"
]
},
"autoload-dev" : {
"files": ["source/CAS.php"],
"psr-4" : {
"PhpCas\\" : "test/CAS/"
}
},
"scripts" : {
"test" : "phpunit",
"phpstan" : "phpstan"
},
"extra" : {
"branch-alias" : {
"dev-master" : "1.3.x-dev"
}
}
}

View File

@@ -20,7 +20,7 @@
*
*
* Interface class of the phpCAS library
* PHP Version 5
* PHP Version 7
*
* @file CAS/CAS.php
* @category Authentication
@@ -35,6 +35,7 @@
* @ingroup public
*/
use Psr\Log\LoggerInterface;
//
// hack by Vangelis Haniotakis to handle the absence of $_SERVER['REQUEST_URI']
@@ -44,11 +45,6 @@ if (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SCRIPT_NAME']) && isset($
$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
}
// Add a E_USER_DEPRECATED for php versions <= 5.2
if (!defined('E_USER_DEPRECATED')) {
define('E_USER_DEPRECATED', E_USER_NOTICE);
}
// ########################################################################
// CONSTANTS
@@ -61,7 +57,7 @@ if (!defined('E_USER_DEPRECATED')) {
/**
* phpCAS version. accessible for the user by phpCAS::getVersion().
*/
define('PHPCAS_VERSION', '1.3.7');
define('PHPCAS_VERSION', '1.6.0');
/**
* @addtogroup public
@@ -140,11 +136,6 @@ define("SAML_SOAP_ENV_CLOSE", '</SOAP-ENV:Envelope>');
*/
define("SAML_ATTRIBUTES", 'SAMLATTRIBS');
/**
* SAML Attributes
*/
define("DEFAULT_ERROR", 'Internal script failure');
/** @} */
/**
* @addtogroup publicPGTStorage
@@ -224,6 +215,8 @@ define("PHPCAS_LANG_JAPANESE", 'CAS_Languages_Japanese');
define("PHPCAS_LANG_SPANISH", 'CAS_Languages_Spanish');
define("PHPCAS_LANG_CATALAN", 'CAS_Languages_Catalan');
define("PHPCAS_LANG_CHINESE_SIMPLIFIED", 'CAS_Languages_ChineseSimplified');
define("PHPCAS_LANG_GALEGO", 'CAS_Languages_Galego');
define("PHPCAS_LANG_PORTUGUESE", 'CAS_Languages_Portuguese');
/** @} */
@@ -261,7 +254,7 @@ define('DEFAULT_DEBUG_DIR', gettmpdir()."/");
/** @} */
// include the class autoloader
require_once dirname(__FILE__) . '/CAS/Autoload.php';
require_once __DIR__ . '/CAS/Autoload.php';
/**
* The phpCAS class is a simple container for the phpCAS library. It provides CAS
@@ -330,12 +323,22 @@ class phpCAS
/**
* phpCAS client initializer.
*
* @param string $server_version the version of the CAS server
* @param string $server_hostname the hostname of the CAS server
* @param int $server_port the port the CAS server is running on
* @param string $server_uri the URI the CAS server is responding on
* @param bool $changeSessionID Allow phpCAS to change the session_id (Single
* Sign Out/handleLogoutRequests is based on that change)
* @param string $server_version the version of the CAS server
* @param string $server_hostname the hostname of the CAS server
* @param int $server_port the port the CAS server is running on
* @param string $server_uri the URI the CAS server is responding on
* @param string|string[]|CAS_ServiceBaseUrl_Interface
* $service_base_url the base URL (protocol, host and the
* optional port) of the CAS client; pass
* in an array to use auto discovery with
* an allowlist; pass in
* CAS_ServiceBaseUrl_Interface for custom
* behavior. Added in 1.6.0. Similar to
* serverName config in other CAS clients.
* @param bool $changeSessionID Allow phpCAS to change the session_id
* (Single Sign Out/handleLogoutRequests
* is based on that change)
* @param \SessionHandlerInterface $sessionHandler the session handler
*
* @return void a newly created CAS_Client object
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
@@ -343,7 +346,8 @@ class phpCAS
* and phpCAS::setDebug()).
*/
public static function client($server_version, $server_hostname,
$server_port, $server_uri, $changeSessionID = true
$server_port, $server_uri, $service_base_url,
$changeSessionID = true, \SessionHandlerInterface $sessionHandler = null
) {
phpCAS :: traceBegin();
if (is_object(self::$_PHPCAS_CLIENT)) {
@@ -362,8 +366,8 @@ class phpCAS
// initialize the object $_PHPCAS_CLIENT
try {
self::$_PHPCAS_CLIENT = new CAS_Client(
$server_version, false, $server_hostname, $server_port, $server_uri,
$changeSessionID
$server_version, false, $server_hostname, $server_port, $server_uri, $service_base_url,
$changeSessionID, $sessionHandler
);
} catch (Exception $e) {
phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
@@ -374,12 +378,22 @@ class phpCAS
/**
* phpCAS proxy initializer.
*
* @param string $server_version the version of the CAS server
* @param string $server_hostname the hostname of the CAS server
* @param int $server_port the port the CAS server is running on
* @param string $server_uri the URI the CAS server is responding on
* @param bool $changeSessionID Allow phpCAS to change the session_id (Single
* Sign Out/handleLogoutRequests is based on that change)
* @param string $server_version the version of the CAS server
* @param string $server_hostname the hostname of the CAS server
* @param string $server_port the port the CAS server is running on
* @param string $server_uri the URI the CAS server is responding on
* @param string|string[]|CAS_ServiceBaseUrl_Interface
* $service_base_url the base URL (protocol, host and the
* optional port) of the CAS client; pass
* in an array to use auto discovery with
* an allowlist; pass in
* CAS_ServiceBaseUrl_Interface for custom
* behavior. Added in 1.6.0. Similar to
* serverName config in other CAS clients.
* @param bool $changeSessionID Allow phpCAS to change the session_id
* (Single Sign Out/handleLogoutRequests
* is based on that change)
* @param \SessionHandlerInterface $sessionHandler the session handler
*
* @return void a newly created CAS_Client object
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
@@ -387,7 +401,8 @@ class phpCAS
* and phpCAS::setDebug()).
*/
public static function proxy($server_version, $server_hostname,
$server_port, $server_uri, $changeSessionID = true
$server_port, $server_uri, $service_base_url,
$changeSessionID = true, \SessionHandlerInterface $sessionHandler = null
) {
phpCAS :: traceBegin();
if (is_object(self::$_PHPCAS_CLIENT)) {
@@ -406,8 +421,8 @@ class phpCAS
// initialize the object $_PHPCAS_CLIENT
try {
self::$_PHPCAS_CLIENT = new CAS_Client(
$server_version, true, $server_hostname, $server_port, $server_uri,
$changeSessionID
$server_version, true, $server_hostname, $server_port, $server_uri, $service_base_url,
$changeSessionID, $sessionHandler
);
} catch (Exception $e) {
phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
@@ -435,6 +450,24 @@ class phpCAS
* @{
*/
/**
* Set/unset PSR-3 logger
*
* @param LoggerInterface $logger the PSR-3 logger used for logging, or
* null to stop logging.
*
* @return void
*/
public static function setLogger($logger = null)
{
if (empty(self::$_PHPCAS_DEBUG['unique_id'])) {
self::$_PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4);
}
self::$_PHPCAS_DEBUG['logger'] = $logger;
self::$_PHPCAS_DEBUG['indent'] = 0;
phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************');
}
/**
* Set/unset debug mode
*
@@ -442,9 +475,13 @@ class phpCAS
* to stop debugging.
*
* @return void
*
* @deprecated
*/
public static function setDebug($filename = '')
{
trigger_error('phpCAS::setDebug() is deprecated in favor of phpCAS::setLogger().', E_USER_DEPRECATED);
if ($filename != false && gettype($filename) != 'string') {
phpCAS :: error('type mismatched for parameter $dbg (should be false or the name of the log file)');
}
@@ -518,14 +555,7 @@ class phpCAS
$indent_str = ".";
if (!empty(self::$_PHPCAS_DEBUG['filename'])) {
// Check if file exists and modifiy file permissions to be only
// readable by the webserver
if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) {
touch(self::$_PHPCAS_DEBUG['filename']);
// Chmod will fail on windows
@chmod(self::$_PHPCAS_DEBUG['filename'], 0600);
}
if (isset(self::$_PHPCAS_DEBUG['logger']) || !empty(self::$_PHPCAS_DEBUG['filename'])) {
for ($i = 0; $i < self::$_PHPCAS_DEBUG['indent']; $i++) {
$indent_str .= '| ';
@@ -533,7 +563,20 @@ class phpCAS
// allow for multiline output with proper identing. Usefull for
// dumping cas answers etc.
$str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str);
error_log(self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2 . "\n", 3, self::$_PHPCAS_DEBUG['filename']);
$str3 = self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2;
if (isset(self::$_PHPCAS_DEBUG['logger'])) {
self::$_PHPCAS_DEBUG['logger']->info($str3);
}
if (!empty(self::$_PHPCAS_DEBUG['filename'])) {
// Check if file exists and modifiy file permissions to be only
// readable by the webserver
if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) {
touch(self::$_PHPCAS_DEBUG['filename']);
// Chmod will fail on windows
@chmod(self::$_PHPCAS_DEBUG['filename'], 0600);
}
error_log($str3 . "\n", 3, self::$_PHPCAS_DEBUG['filename']);
}
}
}
@@ -567,8 +610,6 @@ class phpCAS
}
if (self::$_PHPCAS_VERBOSE) {
echo "<br />\n<b>phpCAS error</b>: <font color=\"FF0000\"><b>" . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . "</b></font> in <b>" . $file . "</b> on line <b>" . $line . "</b><br />\n";
} else {
echo "<br />\n<b>Error</b>: <font color=\"FF0000\"><b>". DEFAULT_ERROR ."</b><br />\n";
}
phpCAS :: trace($msg . ' in ' . $file . 'on line ' . $line );
phpCAS :: traceEnd();
@@ -1869,6 +1910,14 @@ class phpCAS
}
}
/**
* @return CAS_Client
*/
public static function getCasClient()
{
return self::$_PHPCAS_CLIENT;
}
/**
* For testing purposes, use this method to set the client to a test double
*

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/AuthenticationException.php
* @category Authentication
@@ -72,11 +72,15 @@ implements CAS_Exception
phpCAS::traceBegin();
$lang = $client->getLangObj();
$client->printHTMLHeader($lang->getAuthenticationFailed());
printf(
$lang->getYouWereNotAuthenticated(),
htmlentities($client->getURL()),
isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:''
);
if (phpCAS::getVerbose()) {
printf(
$lang->getYouWereNotAuthenticated(),
htmlentities($client->getURL()),
$_SERVER['SERVER_ADMIN'] ?? ''
);
}
phpCAS::trace($messages[] = 'CAS URL: '.$cas_url);
phpCAS::trace($messages[] = 'Authentication failure: '.$failure);
if ( $no_response ) {

View File

@@ -3,7 +3,7 @@
/**
* Autoloader Class
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Autoload.php
* @category Authentication
@@ -26,18 +26,24 @@ function CAS_autoload($class)
// Static to hold the Include Path to CAS
static $include_path;
// Check only for CAS classes
if (substr($class, 0, 4) !== 'CAS_') {
if (substr($class, 0, 4) !== 'CAS_' && substr($class, 0, 7) !== 'PhpCas\\') {
return false;
}
// Setup the include path if it's not already set from a previous call
if (empty($include_path)) {
$include_path = array(dirname(dirname(__FILE__)), dirname(dirname(__FILE__)) . '/../test/' );
$include_path = array(dirname(__DIR__));
}
// Declare local variable to store the expected full path to the file
foreach ($include_path as $path) {
$file_path = $path . '/' . str_replace('_', '/', $class) . '.php';
$class_path = str_replace('_', DIRECTORY_SEPARATOR, $class);
// PhpCas namespace mapping
if (substr($class_path, 0, 7) === 'PhpCas\\') {
$class_path = 'CAS' . DIRECTORY_SEPARATOR . substr($class_path, 7);
}
$file_path = $path . DIRECTORY_SEPARATOR . $class_path . '.php';
$fp = @fopen($file_path, 'r', true);
if ($fp) {
fclose($fp);
@@ -54,6 +60,7 @@ function CAS_autoload($class)
return true;
}
}
$e = new Exception(
'Class ' . $class . ' could not be loaded from ' .
$file_path . ', file does not exist (Path="'
@@ -61,22 +68,22 @@ function CAS_autoload($class)
);
$trace = $e->getTrace();
if (isset($trace[2]) && isset($trace[2]['function'])
&& in_array($trace[2]['function'], array('class_exists', 'interface_exists'))
&& in_array($trace[2]['function'], array('class_exists', 'interface_exists', 'trait_exists'))
) {
return false;
}
if (isset($trace[1]) && isset($trace[1]['function'])
&& in_array($trace[1]['function'], array('class_exists', 'interface_exists'))
&& in_array($trace[1]['function'], array('class_exists', 'interface_exists', 'trait_exists'))
) {
return false;
}
die ((string) $e);
}
// set up __autoload
if (!(spl_autoload_functions())
|| !in_array('CAS_autoload', spl_autoload_functions())
) {
// Set up autoload if not already configured by composer.
if (!class_exists('CAS_Client'))
{
trigger_error('phpCAS autoloader is deprecated. Install phpCAS using composer instead.', E_USER_DEPRECATED);
spl_autoload_register('CAS_autoload');
if (function_exists('__autoload')
&& !in_array('__autoload', spl_autoload_functions())
@@ -86,5 +93,3 @@ if (!(spl_autoload_functions())
spl_autoload_register('__autoload');
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/CookieJar.php
* @category Authentication
@@ -231,6 +231,7 @@ class CAS_CookieJar
case 'commenturl':
case 'discard':
case 'httponly':
case 'samesite':
$cookie[$attributeNameLC] = $attributeValue;
break;
default:

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Exception.php
* @category Authentication
@@ -56,4 +56,4 @@ interface CAS_Exception
{
}
?>
?>

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/GracefullTerminationException.php
* @category Authentication
@@ -83,4 +83,4 @@ implements CAS_Exception
}
}
?>
?>

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/InvalidArgumentException.php
* @category Authentication
@@ -43,4 +43,4 @@ implements CAS_Exception
{
}
?>
?>

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/Catalan.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/ChineseSimplified.php
* @category Authentication
@@ -111,4 +111,4 @@ class CAS_Languages_ChineseSimplified implements CAS_Languages_LanguageInterface
{
return '服务器 <b>%s</b> 不可用(<b>%s</b>)。';
}
}
}

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/English.php
* @category Authentication
@@ -111,4 +111,4 @@ class CAS_Languages_English implements CAS_Languages_LanguageInterface
{
return 'The service `<b>%s</b>\' is not available (<b>%s</b>).';
}
}
}

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/French.php
* @category Authentication
@@ -113,4 +113,4 @@ class CAS_Languages_French implements CAS_Languages_LanguageInterface
}
}
?>
?>

View File

@@ -0,0 +1,117 @@
<?php
/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 7
*
* @file CAS/Language/Galego.php
* @category Authentication
* @package PhpCAS
* @author Enrique Huelva Rivero enrique.huelvarivero@plexus.es
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wiki.jasig.org/display/CASC/phpCAS
*/
/**
* Galego language class
*
* @class CAS_Languages_Galego
* @category Authentication
* @package PhpCAS
* @author Enrique Huelva Rivero enrique.huelvarivero@plexus.es
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://wiki.jasig.org/display/CASC/phpCAS
*
* @sa @link internalLang Internationalization @endlink
* @ingroup internalLang
*/
class CAS_Languages_Galego implements CAS_Languages_LanguageInterface
{
/**
* Get the using server string
*
* @return string using server
*/
public function getUsingServer()
{
return 'usando servidor';
}
/**
* Get authentication wanted string
*
* @return string authentication wanted
*/
public function getAuthenticationWanted()
{
return 'Autenticación CAS necesaria!';
}
/**
* Get logout string
*
* @return string logout
*/
public function getLogout()
{
return 'Saída CAS necesaria!';
}
/**
* Get the should have been redirected string
*
* @return string should habe been redirected
*/
public function getShouldHaveBeenRedirected()
{
return 'Xa debería ser redireccionado ao servidor CAS. Faga click <a href="%s">aquí</a> para continuar';
}
/**
* Get authentication failed string
*
* @return string authentication failed
*/
public function getAuthenticationFailed()
{
return 'Autenticación CAS errada!';
}
/**
* Get the your were not authenticated string
*
* @return string not authenticated
*/
public function getYouWereNotAuthenticated()
{
return '
<p>Non estás autenticado</p><p>Podes volver tentalo facendo click <a href="%s">aquí</a>.</p><p>Se o problema persiste debería contactar con el <a href="mailto:%s">administrador deste sitio</a>.</p>';
}
/**
* Get the service unavailable string
*
* @return string service unavailable
*/
public function getServiceUnavailable()
{
return 'O servizo `<b>%s</b>\' non está dispoñible (<b>%s</b>).';
}
}
?>

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/German.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/Greek.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/Japanese.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/LanguageInterface.php
* @category Authentication
@@ -93,4 +93,4 @@ interface CAS_Languages_LanguageInterface
public function getServiceUnavailable();
}
?>
?>

View File

@@ -0,0 +1,114 @@
<?php
/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 7
*
* @file CAS/Language/Portuguese.php
* @category Authentication
* @package PhpCAS
* @author Sherwin Harris <sherwin.harris@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://apereo.atlassian.net/wiki/spaces/CASC/pages/103252517/phpCAS
*/
/**
* Portuguese language class
*
* @class CAS_Languages_Portuguese
* @category Authentication
* @package PhpCAS
* @author Sherwin Harris <sherwin.harris@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://apereo.atlassian.net/wiki/spaces/CASC/pages/103252517/phpCAS
*
* @sa @link internalLang Internationalization @endlink
* @ingroup internalLang
*/
class CAS_Languages_Portuguese implements CAS_Languages_LanguageInterface
{
/**
* Get the using server string
*
* @return string using server
*/
public function getUsingServer()
{
return 'Usando o servidor';
}
/**
* Get authentication wanted string
*
* @return string authentication wanted
*/
public function getAuthenticationWanted()
{
return 'A autenticação do servidor CAS desejado!';
}
/**
* Get logout string
*
* @return string logout
*/
public function getLogout()
{
return 'Saida do servidor CAS desejado!';
}
/**
* Get the should have been redirected string
*
* @return string should have been redirected
*/
public function getShouldHaveBeenRedirected()
{
return 'Você já deve ter sido redirecionado para o servidor CAS. Clique <a href="%s">aqui</a> para continuar';
}
/**
* Get authentication failed string
*
* @return string authentication failed
*/
public function getAuthenticationFailed()
{
return 'A autenticação do servidor CAS falheu!';
}
/**
* Get the your were not authenticated string
*
* @return string not authenticated
*/
public function getYouWereNotAuthenticated()
{
return '<p>Você não foi autenticado.</p><p>Você pode enviar sua solicitação novamente clicando <a href="%s">aqui</a>. </p><p>Se o problema persistir, você pode entrar em contato com <a href="mailto:%s">o administrador deste site</a>.</p>';
}
/**
* Get the service unavailable string
*
* @return string service unavailable
*/
public function getServiceUnavailable()
{
return 'O serviço `<b>%s</b>\' não está disponível (<b>%s</b>).';
}
}

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/Language/Spanish.php
* @category Authentication

View File

@@ -18,7 +18,7 @@
* limitations under the License.
*
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/OutOfSequenceBeforeAuthenticationCallException.php
* @category Authentication

View File

@@ -18,7 +18,7 @@
* limitations under the License.
*
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/OutOfSequenceBeforeClientException.php
* @category Authentication

View File

@@ -18,7 +18,7 @@
* limitations under the License.
*
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/OutOfSequenceBeforeProxyException.php
* @category Authentication

View File

@@ -18,7 +18,7 @@
* limitations under the License.
*
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/OutOfSequenceException.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/PGTStorage/AbstractStorage.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/PGTStorage/Db.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/PGTStorage/AbstractStorage.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Abstract.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Exception.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Http.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Http/Abstract.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Http/Get.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Http/Post.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Imap.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxiedService/Testabel.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxyChain.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxyChain/AllowedList.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxyChain/Any.php
* @category Authentication

View File

@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PHP Version 5
* PHP Version 7
*
* @file CAS/ProxyChain/Interface.php
* @category Authentication
@@ -50,4 +50,4 @@ interface CAS_ProxyChain_Interface
*/
public function matches(array $list);
}
}

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