Compare commits

...

264 Commits
3.0.2-1 ... tag

Author SHA1 Message Date
jf-cbd
5232694c04 N°8281 - Unable to click search button in multiple request template 2025-03-21 11:20:37 +01:00
jf-cbd
874a5fd2ce Dump autoloader 2025-03-12 11:36:06 +01:00
jf-cbd
063bb9680e N°8231 - Better variable fallback 2025-03-06 16:09:51 +01:00
jf-cbd
8f8ac46f55 N°8215 - When PHP warning are enabled, Global Request doesn't work 2025-03-06 11:59:08 +01:00
denis.flaven@combodo.com
07b904ee1b N°8231 - making rest api logs more readable 2025-03-06 11:59:08 +01:00
denis.flaven@combodo.com
affed69999 Version number bump. 2025-02-07 10:09:48 +01:00
denis.flaven@combodo.com
d5754fc568 N°8135 - Bump datamodel version. 2025-01-31 17:04:56 +01:00
jf-cbd
44290db312 N°8134 - Portal user profile is broken, regression from 7776 2025-01-28 10:23:44 +01:00
jf-cbd
c49ceae75e Fix HandleForm call 2025-01-21 16:46:15 +01:00
jf-cbd
8980f627e9 Fix format 2025-01-21 12:09:06 +01:00
jf-cbd
160bfd714b N°7776 remove twig from ajax calls 2025-01-20 15:41:22 +01:00
jf-cbd
8d58372074 Update unattended installation script documentation 2025-01-16 17:13:26 +01:00
Eric Espie
37fc1a5723 N°7810 - security hardening 2024-12-27 09:04:28 +01:00
jf-cbd
95aa444ee6 Security hardening 2024-12-13 16:48:13 +01:00
jf-cbd
f5de808c7c Security hardening (#685)
* security hardening
2024-12-13 15:09:18 +01:00
Benjamin Dalsass
e03033ce52 N°7219 - Fatal error following dashboard modification when dashboard title contains an é 2024-11-27 09:40:22 +01:00
jf-cbd
374b35f78a 🚀 Fix GitHub action 2024-11-07 14:50:46 +01:00
jf-cbd
04bd8cc5ce 🚀 Update GitHub actions to improve PR classification 2024-10-22 16:07:47 +02:00
denis.flaven@combodo.com
8cb701bda3 🔖 Prepare 2.7.11 version 2024-09-26 16:53:24 +02:00
jf-cbd
1b29746806 Rename github token 2024-09-23 17:14:41 +02:00
jf-cbd
fb9c317256 Add an action in the workflow to automatically add pull requests to the Combodo PRs dashboard 2024-09-23 14:43:33 +02:00
Eric Espie
0904a21e3f Cleanup ItopTestCase 2024-06-24 11:50:37 +02:00
Molkobain
82d11eeb47 N°7127 - Upgrade handlebars.js to v4.7.8 2024-06-21 11:19:39 +02:00
Eric Espie
142d6c8993 N°7533 - Detect and warns on Galera clusters 2024-06-20 11:06:57 +02:00
Timothee
320922a13d N°7545 Correctly display error message 2024-06-17 16:49:33 +02:00
Eric Espie
f03d731b1d N°7533 - Prevent installation of iTop on Galera clusters 2024-06-12 16:14:23 +02:00
Eric Espie
8be7628668 N°7548 - Code hardening 2024-05-29 18:11:36 +02:00
Eric Espie
62caf16153 N°7364 - Code hardening 2024-05-21 14:20:30 +02:00
odain
163a3afc0f N°7426 - no session created - replace php_sapi_name() by PHP_SAPI in unattended 2024-05-16 15:31:08 +02:00
odain
f8b54be896 N°7426 - no session created - replace php_sapi_name() by PHP_SAPI 2024-05-16 14:10:54 +02:00
Romain Quetiez
53dc452d61 Avoid unnecessary custom test environment compilations (base compilation of file modification time) 2024-05-16 09:53:04 +02:00
Romain Quetiez
ccaf2dc5b7 Make the tests compatible with windows (and linux) 2024-05-16 09:53:04 +02:00
Molkobain
5d5df5ad1a N°7255 - Fix misc. stylesheets not working in portal since N°7047 2024-05-07 10:37:39 +02:00
Molkobain
32140b360f Cherry pick fixes from 59a955f4 2024-04-29 11:45:09 +02:00
jf-cbd
d85767a838 Update test to run only on commmunity builds 2024-04-24 11:14:40 +02:00
jf-cbd
eeec57536b Security hardening 2024-04-23 11:55:39 +02:00
Molkobain
16ff6341d0 N°7455 - Fix regression from 4c784886, wrong class tested 2024-04-19 09:14:53 +02:00
Molkobain
9dab8679d6 N°7448 - Update dictionary entry 2024-04-18 18:44:20 +02:00
Molkobain
4c78488644 N°7455 - Ensure form renderer class extends FormRenderer 2024-04-18 18:15:02 +02:00
odain
b65e931c4c N°7439 - setup wizard broken on essential targets after fresh install via unattended CLI 2024-04-15 09:57:54 +02:00
odain
6cb3519308 N°7407 - test readability 2024-04-12 16:59:56 +02:00
odain
cfb9fae648 N°7407 - fix previous commit 2024-04-12 16:49:11 +02:00
odain
f4e791734f N°7407 - remove phpunit annotation 2024-04-12 14:49:20 +02:00
odain
6653ab0668 N°7407 - fix missing extension installation via unattended from production-modules or extension folder 2024-04-12 13:25:40 +02:00
odain
7ab258ba03 N°7439 - setup wizard broken on essential targets 2024-04-12 11:32:04 +02:00
odain
b5af30a93f N°7407 - refactor unattended tests to make work anywhere 2024-04-12 11:32:04 +02:00
Molkobain
bbfa601ab1 N°7446 - Add temporary workaround to fix an issue due to DB views creation. 2024-04-12 09:59:14 +02:00
odain
172b1cb1ff N°7407 - add phpunit annotation to exclude tests on top of targets 2024-04-11 18:54:43 +02:00
Molkobain
ca356859a3 N°7446 - Add comment for better understanding 2024-04-11 16:55:48 +02:00
Molkobain
5efe294895 N°7446 - Fix custom datamodel test class not creating DB tables correctly 2024-04-11 16:50:27 +02:00
odain
e0170ccc7e N°7407 - InstallationFileServiceTest relies on local installation.xml to reduce maintenance 2024-04-10 14:05:39 +02:00
odain
3b78885f38 N°7407 - unattended test cleanup for PHP 8.x deprecations 2024-04-09 11:06:03 +02:00
odain
ed562c9f73 N°7407 - fix unattended test enhancement 2024-04-09 10:02:06 +02:00
odain-cbd
85c576a986 N°7407 - N°7306 - Ease iTop installation via unattended CLI by using installation.xml choices (#641)
* N°7306 - Use iTop configuration settings to run unattended installation (instead of XML file settings)

* 7306 - fix infinite loop with db_tls.ca null

* 7306 - complete fields to use from itop configuration instead of XML setup

* fix using default language from conf

* 6365 - temp work

* 6365 - add option to select modules from installation.xml

* 6365-select modules option in unattended install

* 6365 - pass env to service + debug failed test

* 6365 - debug ci again + separate process annotation

* 6365 - fix test + cleanup

* 6365 - ci using use_installation_xml mode

* 6365 - ci using use_installation_xml mode

* 6365 - pass selected_modules to unattended

* N°6365 - Compute selected modules based on selected extensions coming from XML setup

* switch constr parameters and fix call from unattended cli

* 6365 - use use_installation_xml for unattended install only when no selected modules already provided

* test ci XML setup including selected extensions but no modules

* test ci installing iTop without selected modules/extenesions: guess via installation.xml

* same but without even providing XML setup - comment it in ci_description.ini

* 7306 - cleanup requires

* use infra master

* N°6365 - make current unattended CLI work with any iTop version (CLIPage compatibility)

* N°6365 - log which modules will be installed during setup

* N°6365 - unattended documentation + bash helper

* 6365- fix warning due to copies index access

* 6365 - enhance traces feedback to understant which and how modules are computes

* 6365 - enhance bash CLI + doc

* 6365 - fix require clipage compatibility

* 6365 - add return for better cli ouput

* 6365 - enhance ouput messages

* Document the usage and harmonize argument names (still not perfect)

* 6365 - fix use of new param param-file

* 6365 - fix test + vardump cleanup

* N°6365 - use underscore for unattended install options as advices in the PR

* 6365 -enhance test by using PHP_BINARY

---------

Co-authored-by: Romain Quetiez <romain.quetiez@combodo.com>
2024-04-09 10:00:58 +02:00
odain-cbd
5a34c76cc4 N°5120 - Unattended install : use cty toolkit version (#624)
* N°5120 - Unattended install : use cty toolkit version

* N°5120 - CLI mode validation

* 5120 - bring CI enhancements

* 5120 - bring back saas use-itop-config option: to read db settings from conf directly

* 5120 - move unattended script in setup folder/unattended-install

* 5120 - use-itop-config option: take db_tls_enabled and db_tls_ca into account

* 5120 - move test

* 5120 - put ci enhancements back - logs and conf preservation with install mode

* 5120 - keep PR simple - remove saas use-itop-config option for now

* 5120 - remove ci enhancement to preserve configuration

* Update setup/unattended-install/README.md

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>

* 5120 - documentation

* 5120 - fix log level

* 5120 - fix test

* fix phpunit test comment

---------

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
2024-03-29 11:09:27 +01:00
Molkobain
da99a250bf N°7075 - Add check for Content Security Policies (CSP) in the setup 2024-03-21 14:20:22 +01:00
Pierre Goiffon
dbd5ba0377 N°7302 Fix SetupUtilsTest::testHumanReadableSize 2024-03-13 09:56:31 +01:00
Pierre Goiffon
5d6f293956 N°7302 Report SetupUtilsTest::testHumanReadableSize in the 2.7 branch 2024-03-13 09:48:46 +01:00
Pierre Goiffon
986c24d777 N°7302 Fix unit name in \SetupUtils::HumanReadableSize (#626) 2024-03-12 18:09:29 +01:00
Pierre Goiffon
763112c179 N°7344 rest.php core/get : add try/catch around query execution (#622)
Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2024-03-12 18:08:04 +01:00
Pierre Goiffon
a5efd981d8 N°7343 Catch ParseError when loading dict files in setup (#615) 2024-03-12 18:05:38 +01:00
Pierre Goiffon
2922b22478 \DBObject::GetSearchForUniquenessRule remove wrong @api 2024-03-12 17:34:28 +01:00
Pierre Goiffon
2af05a437e N°4314 - Fix Uniqueness rules not working with Silo
(cherry picked from commit e8c11f38d2)
2024-03-12 17:32:56 +01:00
Molkobain
a9f8dcc5e8 N°7122 N°4164 - Portal: Hide log off button when user can't actually log off (eg. SSO using SAML or other providers) (#599)
* N°7122 N°4164 - Portal: Hide log off button when user can't actually log off (eg. SSO using SAML or other providers)

* N°7122 - Fix typo

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>

---------

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
2024-03-11 21:13:09 +01:00
Pierre Goiffon
c325294e17 N°6599 Update moment from 2.22.2 to 2.30.1 2024-03-11 16:39:05 +01:00
Pierre Goiffon
da490739be 💡 Fix \CMDBSource::Query phpdoc block 2024-03-07 11:10:53 +01:00
Pierre Goiffon
b867faa355 Fix DBObjectTest
Error was "Class 'Combodo\iTop\Test\UnitTest\Core\DBObjectSet' not found"
2024-03-04 08:09:35 +01:00
jf-cbd
7453cc184f ✏️ fix a typo 2024-03-01 17:25:08 +01:00
Anne-Catherine
473cf004b6 N°6968 - Audit duration : add of a rule multiplie by 4 the time of response (#575) 2024-02-29 16:33:04 +01:00
Pierre Goiffon
f6fec506b1 💡 Some PHPDoc hints on value types to pass to DBObject::Set 2024-02-20 12:10:00 +01:00
Molkobain
5c12151c26 📝 Update PR template 2024-02-19 09:46:12 +01:00
Pierre Goiffon
5d6c4939f6 N°7245 Bettor logs on RunTimeEnvironment::CallInstallerHandlers exceptions (#606) 2024-02-14 11:01:12 +01:00
Molkobain
1b3a2c8470 N°5775 - Show error message to user in case of issue during token generation 2024-01-24 14:49:51 +01:00
Dennis Lassiter
618d8e6468 N°5775 - Allow configuration of OAuth client on MS Azure with single tenant (#553)
* Add Tenant-Support for Azure OAuthClient

* Improvement: Make tenant required

* Improvment: Removed check for null-value

Since last commit, the "tenant"-field either set to a custom value or "common" by default. It is not allowed to be null

* Add field description

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2024-01-24 14:38:54 +01:00
Molkobain
01a955a16f 📝 Add PR template 2024-01-16 19:42:39 +01:00
Anne-Catherine
87582a021b N°6993 - Error message on bulk transition on object containing a null blob (#596) 2024-01-15 15:49:19 +01:00
odain
9830178a47 N°7085 - ci restore runTestsInSeparateProcesses 2024-01-12 08:36:15 +01:00
odain-cbd
c140ebcb6b N°7085 - Fix infinite loop in login page until fatal error occurs (#592)
* N°7085 - login page infinite loop until fatal error- add Config->AddAllowedLoginTypes

* N°7085 - reproduce issue via a test

* N°7085-fix infinite loop

* N°7085 - ci: fix config file rights in tearDown

* N°7085 - ci: fix config file rights in tearDown (again)

* N°7085 - ci: fix config file content

* N°7085 - ci : add runTestsInSeparateProcesses

* Update core/config.class.inc.php

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

* N°7085 - exit -1 + enhance log message

* PR feedbacks from Romain regarding LoginTest annotations

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2024-01-12 08:13:40 +01:00
Molkobain
7a0a4e377b N°7137 - Fix regression from 7fffbb60, missing "default_value" parameter 2024-01-11 16:40:40 +01:00
Thomas Casteleyn
7fffbb60e9 N°7137 - DataSynchro: Remove "Organization" as default value for SynchroReplica->dest_class (#551) 2024-01-11 15:38:02 +01:00
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
Molkobain
583ab98210 Fix typo 2022-09-21 16:11:24 +02:00
Pierre Goiffon
88d743b1cc N°5538 Make PHPUnit test fail if transaction opened but not closed 2022-09-21 14:05:27 +02:00
Pierre Goiffon
7ac4bc95bb ItopDataTestCase : improve log message 2022-09-21 11:51:07 +02:00
Pierre Goiffon
d431811725 N°4947 Fix Email always picking "production" env config file (#331)
Note that the code was duplicated in both Email* impl, this is refactored : LoadConfig and m_oConfig are pulled up in Email, and SetRecipientFrom() calls are also refactored in Email
2022-09-14 15:33:48 +02:00
xtophe38
7512f721e9 Setup wizard : use the ITOP_APPLICATION constant instead of hardcoded "iTop" string (#335)
* Update wizardsteps.class.inc.php

* Done as requested

* Fix typo for better readability/genericity

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2022-09-13 20:11:38 +02:00
Pierre Goiffon
bdfe3a3b35 N°5235 Tmp dir setup check is now non blocking (woops previous commit was incomplete) (#301) 2022-09-13 18:22:29 +02:00
Pierre Goiffon
5cf391c3bb N°5235 - Add non blocking setup check : is tmp dir is writable (#301) 2022-09-13 18:20:35 +02:00
Eric Espie
4c1df9927d N°5394 - use session for the FSM 2022-09-12 09:45:30 +02:00
Pierre Goiffon
74003f12c1 N°5513 Remove test name debug info 2022-09-08 18:20:52 +02:00
Pierre Goiffon
3bc12b0434 N°5513 Improve PHPUnit output to stdout
Will allow to see progress when looking at the Jenkins output
2022-09-08 15:11:02 +02:00
Pierre Goiffon
02a0969b53 N°5414 Fix undefined constant error in notification with wrong placeholder
Fix regression introduced in 33c2168 (#282)
2022-08-29 12:31:45 +02:00
Stephen Abello
d78a25ee4e N°5462 Add a setup check to verify if directory-level configuration files (.htaccess and web.config) are used by the server 2022-08-24 16:33:54 +02:00
Pierre Goiffon
7a6a3d1ac0 Integration tests : move and comment itop-community group 2022-08-24 14:41:32 +02:00
Pierre Goiffon
6b32be0899 N°5216 Fix invalid message-id when sending notification using cron on system with a specific locale set (#297)
The timestamp used was indeed locale dependent.
This commit fixes this behavior by removing the locale dependency using a better printf format (see https://www.php.net/manual/fr/function.sprintf.php)
2022-08-17 14:11:32 +02:00
Pierre Goiffon
33c2168af2 🔊 Adds a debug log for invalid placeholders (#282)
This adds a debug log when a placeholder cannot be replaced.
Before the placeholder was just not replaced.
Now we can enable a debug log on the LogChannels::NOTIFICATION channel.
2022-08-17 14:09:59 +02:00
Lars Kaltefleiter
ae021064a4 🌐 Update German translations for oauth-client (#319) 2022-08-16 17:02:37 +02:00
Benjamin Dalsass
0a61169326 Update version to next release 2.7.8 2022-08-16 09:47:46 +02:00
odain
0001e8ffc4 💚 use new ci validation 2020-10-09 10:13:51 +02:00
447 changed files with 15723 additions and 5425 deletions

83
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,83 @@
<!--
IMPORTANT: Please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
Any PRs not following the guidelines or with missing information will not be considered.
-->
## Base information
| Question | Answer
|---------------------------------------------------------------|--------
| Related to a SourceForge thead / Another PR / Combodo ticket? | <!-- Put the URL -->
| Type of change? | Bug fix / Enhancement / Translations
## Symptom (bug) / Objective (enhancement)
<!--
If it's a bug
- Explain the symptom in details
- If possible put error messages, logs or screenshots (you can paste image directly in this editor).
If it's an enhancement
- Describe what is blocking you, what is the objective with as much details as possible.
- Add screenshots if it's related to UI.
-->
## Reproduction procedure (bug)
<!--
Remove this section only if it's NOT a bug.
Otherwise, explain step by step how to reproduce the issue on a standard iTop Community.
If it requires a custom datamodel, provide the minimal XML delta to reproduce it on a standard iTop Community.
-->
1. On iTop x.y.z <!-- Put complete iTop version (eg. 3.1.0-2) -->
2. With PHP x.y.z <!-- Put complete PHP version (eg. 8.1.24) -->
2. First go there
2. Then do that
3. ...
4. Finally, see that...
## Cause (bug)
<!--
Remove this section only if it's NOT a bug.
Otherwise, explain what is the cause of the issue (where in the code and why)
-->
## Proposed solution (bug and enhancement)
<!--
Explain in details how you are proposing to solve this:
- What did you do in the code and why
- If you changed something in the UI, put before / after screenshots (you can paste image directly in this editor)
-->
## Checklist before requesting a review
<!--
Don't remove these lines, check them once done.
-->
- [ ] I have performed a self-review of my code
- [ ] I have tested all changes I made on an iTop instance
- [ ] I have added a unit test, otherwise I have explained why I couldn't
- [ ] Is the PR clear and detailed enough so anyone can understand digging in the code?
## Checklist of things to do before PR is ready to merge
<!--
Things that needs to be done in the PR before it can be considered as ready to be merged
Examples:
- Changes requested in the review
- Unit test to add
- Dictionary entries to translate
- ...
-->
- [ ] ...
- [ ] ...
- [ ] ...

43
.github/workflows/action.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Add PRs to Combodo PRs Dashboard
on:
pull_request_target:
types:
- opened
jobs:
add-to-project:
name: Add PR to Combodo Project
runs-on: ubuntu-latest
steps:
- name: Check if author is a member of the organization
id: check-membership
run: |
ORG="Combodo"
AUTHOR=$(jq -r .pull_request.user.login "$GITHUB_EVENT_PATH")
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
"https://api.github.com/orgs/$ORG/members/$AUTHOR")
if [ "$RESPONSE" == "404" ]; then
echo "project_url=https://github.com/orgs/Combodo/projects/5" >> $GITHUB_ENV
echo "is_member=false" >> $GITHUB_ENV
else
echo "project_url=https://github.com/orgs/Combodo/projects/4" >> $GITHUB_ENV
echo "is_member=true" >> $GITHUB_ENV
fi
- name: Add internal tag if member
if: env.is_member == 'true'
run: |
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
-d '{"labels":["internal"]}'
env:
is_member: ${{ env.is_member }}
- name: Add PR to the appropriate project
uses: actions/add-to-project@v1.0.2
with:
project-url: ${{ env.project_url }}
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}

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

@@ -27,7 +27,7 @@ $iTopFolder = __DIR__."/../../../";
require_once("$iTopFolder/approot.inc.php");
require_once(APPROOT."/application/utils.inc.php");
if (php_sapi_name() !== 'cli')
if (PHP_SAPI !== 'cli')
{
throw new \Exception('This script can only run from CLI');
}
@@ -48,4 +48,4 @@ if (!file_exists($sCssFile))
{
fwrite(STDERR, "Failed to compile $sCssFile, exiting.");
exit(1);
}
}

View File

@@ -26,7 +26,7 @@ $iTopFolder = __DIR__ . "/../../" ;
require_once ("$iTopFolder/approot.inc.php");
require_once (APPROOT."/setup/setuputils.class.inc.php");
if (php_sapi_name() !== 'cli')
if (PHP_SAPI !== 'cli')
{
throw new \Exception('This script can only run from CLI');
}
@@ -70,4 +70,4 @@ if (false === empty($aMissing)) {
echo "Some new tests dirs exists !\n"
.' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n"
.' List of dirs:'."\n".var_export($aMissing, true);
}
}

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
@@ -1255,81 +1316,115 @@ interface iRestServiceProvider
public function ExecOperation($sVersion, $sVerb, $aParams);
}
interface iRestInputSanitizer
{
public function SanitizeJsonInput(string $sJsonInput): string;
}
/**
* 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;
/**
* Sanitize the content of this result to hide sensitive information
*/
public function SanitizeContent()
{
// The default implementation does nothing
}
}
/**
* Helpers for implementing REST services
*
* @api
* @package Extensibility
* @package RESTExtensibilityAPI
*/
class RestUtils
{
@@ -1478,6 +1573,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 +1634,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 +1685,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
@@ -1623,8 +1722,16 @@ class RestUtils
elseif (is_string($key))
{
// OQL
$oSearch = DBObjectSearch::FromOQL($key);
}
try {
$oSearch = DBObjectSearch::FromOQL($key);
} catch (Exception $e) {
throw new CoreOqlException('Query failed to execute', [
'query' => $key,
'exception_class' => get_class($e),
'exception_message' => $e->getMessage(),
]);
}
}
else
{
throw new Exception("Wrong format for key");
@@ -1773,4 +1880,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

@@ -3218,13 +3218,13 @@ EOF
if ($oAttDef->GetEditClass() == 'Document')
{
$oDocument = $this->Get($sAttCode);
if (!$oDocument->IsEmpty())
if (is_object($oDocument) && !$oDocument->IsEmpty())
{
$sDisplayValue = $this->GetAsHTML($sAttCode);
$sDisplayValue .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_',
$oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
$oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
$sDisplayValue .= "<br/>".Dict::Format('UI:DownloadDocument_',
$oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
$oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
}
else
{
@@ -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 = json_encode($this->sId);
$sLayoutClass = json_encode($this->sLayoutClass);
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
$sAutoReloadSec = (string) $this->iAutoReloadSec;
$sTitle = addslashes($this->sTitle);
$sFile = addslashes($this->GetDefinitionFile());
$sTitle = json_encode($this->sTitle);
$sFile = json_encode($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = $this->GetReloadURL();
@@ -1250,15 +1250,15 @@ $('#dashboard_editor').dialog({
});
$('#dashboard_editor .ui-layout-center').runtimedashboard({
dashboard_id: '$sId',
layout_class: '$sLayoutClass',
title: '$sTitle',
dashboard_id: $sId,
layout_class: $sLayoutClass,
title: $sTitle,
auto_reload: $sAutoReload,
auto_reload_sec: $sAutoReloadSec,
submit_to: '$sUrl',
submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
submit_parameters: {operation: 'save_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_to: '$sUrl',
render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_parameters: {operation: 'render_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
new_dashlet_parameters: {operation: 'new_dashlet'}
});
@@ -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'))
@@ -59,15 +59,17 @@ class LoginBasic extends AbstractLoginFSMExtension
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
// Save the checked user
$_SESSION['auth_user'] = $sAuthUser;
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
{
list($sAuthUser) = $this->GetAuthUserAndPassword();
$sAuthUser = $_SESSION['auth_user'];
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -75,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;
@@ -84,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();
@@ -111,6 +117,11 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnConnected(&$iErrorCode)
{
unset($_SESSION['login_temp_auth_user']);
if (is_null(UserRights::GetUserObject())){
//N°7085 avoid infinite loop
IssueLog::Error("No user logged in. exit");
exit(-1);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -126,4 +137,4 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
}
}
}
}
}

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'))
@@ -43,15 +43,17 @@ class LoginExternal extends AbstractLoginFSMExtension
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
// Save the checked user
$_SESSION['auth_user'] = $sAuthUser;
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
{
$sAuthUser = $this->GetAuthUser();
$sAuthUser = $_SESSION['auth_user'];
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -59,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);
@@ -69,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');
@@ -71,6 +75,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
// Save the checked user
$_SESSION['auth_user'] = $sAuthUser;
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -80,17 +86,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
{
if (isset($_SESSION['auth_user']))
{
// If FSM reenter this state (example 2FA) then the auth_user is not resubmitted
$sAuthUser = $_SESSION['auth_user'];
}
else
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
}
$sAuthUser = $_SESSION['auth_user'];
// Store 'auth_user' in session for further use
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
}
@@ -102,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;
}
@@ -114,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');
@@ -58,15 +58,17 @@ class LoginURL extends AbstractLoginFSMExtension
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
// Save the checked user
$_SESSION['auth_user'] = $sAuthUser;
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthUser = $_SESSION['auth_user'];
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -74,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
@@ -130,16 +199,8 @@ class utils
public static function IsModeCLI()
{
$sSAPIName = php_sapi_name();
$sCleanName = strtolower(trim($sSAPIName));
if ($sCleanName == 'cli')
{
return true;
}
else
{
return false;
}
$sCleanName = strtolower(trim(PHP_SAPI));
return ($sCleanName === 'cli');
}
protected static $bPageMode = null;
@@ -247,13 +308,13 @@ class utils
}
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
{
$retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
{
if ($value === $defaultValue)
@@ -269,13 +330,12 @@ class utils
$retValue = $defaultValue;
}
}
return $retValue;
return $retValue;
}
/**
* @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 +344,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 +365,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 +397,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 +405,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 +426,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
}
@@ -401,11 +473,11 @@ class utils
$sMimeType = self::GetFileMimeType($sTmpName);
$oDocument = new ormDocument($doc_content, $sMimeType, $sName);
break;
case UPLOAD_ERR_NO_FILE:
// no file to load, it's a normal case, just return an empty document
break;
case UPLOAD_ERR_FORM_SIZE:
case UPLOAD_ERR_INI_SIZE:
throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
@@ -414,7 +486,7 @@ class utils
case UPLOAD_ERR_PARTIAL:
throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
break;
case UPLOAD_ERR_NO_TMP_DIR:
throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
break;
@@ -427,7 +499,7 @@ class utils
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
break;
default:
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
break;
@@ -535,17 +607,17 @@ class utils
return $aSelectedObj;
}
public static function GetNewTransactionId()
{
return privUITransaction::GetNewTransactionId();
}
public static function IsTransactionValid($sId, $bRemoveTransaction = true)
{
return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction);
}
public static function RemoveTransaction($sId)
{
return privUITransaction::RemoveTransaction($sId);
@@ -730,9 +802,9 @@ class utils
$aDateTokens = array_keys($aSpec);
$aDateRegexps = array_values($aSpec);
}
$sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches))
{
$sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
@@ -749,7 +821,7 @@ class utils
}
// http://www.spaweditor.com/scripts/regex/index.php
}
/**
* Convert an old date/time format specification (using % placeholders)
* to a format compatible with DateTime::createFromFormat
@@ -1168,7 +1240,7 @@ class utils
{
$aArguments['param_file'] = $sParamFile;
}
$aArgs = array();
foreach($aArguments as $sName => $value)
{
@@ -1177,7 +1249,7 @@ class utils
$aArgs[] = "--$sName=".escapeshellarg($value);
}
$sArgs = implode(' ', $aArgs);
$sScript = realpath(APPROOT.$sScriptName);
if (!file_exists($sScript))
{
@@ -1226,13 +1298,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
@@ -1258,7 +1340,7 @@ class utils
public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions, $sTableId = null, $sDataTableId = null)
{
// 1st - add standard built-in menu items
//
//
switch($iMenuId)
{
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
@@ -1283,7 +1365,7 @@ class utils
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
);
}
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO)
{
// Bulk export actions
@@ -1297,7 +1379,7 @@ class utils
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
break;
case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
@@ -1311,7 +1393,7 @@ class utils
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page & CSV Export
@@ -1329,19 +1411,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');
}
@@ -1375,7 +1457,7 @@ class utils
if (is_object($oMenuItem))
{
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
{
$oPage->add_linked_script($sLinkedScript);
@@ -1512,7 +1594,7 @@ class utils
return $sProposed;
}
}
/**
* Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
* @param string $sId The ID to sanitize
@@ -1522,13 +1604,13 @@ class utils
{
return str_replace(array(':', '[', ']', '+', '-'), '_', $sId);
}
/**
* Helper to execute an HTTP POST request
* Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
* originaly named after do_post_request
* Does not require cUrl but requires openssl for performing https POSTs.
*
*
* @param string $sUrl The URL to POST the data to
* @param array $aData The data to POST as an array('param_name' => value)
* @param string $sOptionnalHeaders Additional HTTP headers as a string with newlines between headers
@@ -1539,11 +1621,11 @@ class utils
*
* @return string The result of the POST request
* @throws Exception with a specific error message depending on the cause
*/
*/
public static function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array())
{
// $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
if (function_exists('curl_init'))
{
// If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options
@@ -1577,7 +1659,7 @@ class utils
CURLOPT_POSTFIELDS => http_build_query($aData),
CURLOPT_HTTPHEADER => $aHTTPHeaders,
);
$aAllOptions = $aCurlOptions + $aOptions;
$ch = curl_init($sUrl);
curl_setopt_array($ch, $aAllOptions);
@@ -1603,7 +1685,7 @@ class utils
else
{
// cURL is not available let's try with streams and fopen...
$sData = http_build_query($aData);
$aParams = array('http' => array(
'method' => 'POST',
@@ -1615,7 +1697,7 @@ class utils
$aParams['http']['header'] .= $sOptionnalHeaders;
}
$ctx = stream_context_create($aParams);
$fp = @fopen($sUrl, 'rb', false, $ctx);
if (!$fp)
{
@@ -1656,7 +1738,7 @@ class utils
/**
* Get a standard list of character sets
*
*
* @param array $aAdditionalEncodings Additional values
* @return array of iconv code => english label, sorted by label
*/
@@ -1686,8 +1768,8 @@ class utils
public static function HtmlEntities($sValue)
{
return htmlentities($sValue, ENT_QUOTES, 'UTF-8');
}
}
/**
* Helper to encapsulation iTop's html_entity_decode
* @param string $sValue
@@ -1716,7 +1798,7 @@ class utils
return $e->getMessage();
}
}
/**
* Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags
* and escaping HTML entities
@@ -1729,7 +1811,7 @@ class utils
$sText = str_replace("\r", "\n", $sText);
return str_replace("\n", '<br/>', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
}
/**
* Eventually compiles the SASS (.scss) file into the CSS (.css) file
*
@@ -1792,7 +1874,7 @@ class utils
return $sCss;
}
public static function GetImageSize($sImageData)
{
if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
@@ -1849,7 +1931,7 @@ class utils
case 'image/png':
$img = @imagecreatefromstring($oImage->GetData());
break;
default:
// Unsupported image type, return the image as-is
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
@@ -1863,14 +1945,14 @@ class utils
else
{
// Let's scale the image, preserving the transparency for GIFs and PNGs
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{
@@ -1878,38 +1960,38 @@ class utils
imagealphablending($new, false);
imagesavealpha($new, true);
}
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($oImage->GetMimeType())
{
case 'image/gif':
imagegif($new); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean();
imagedestroy($img);
imagedestroy($new);
return $oResampledImage;
}
}
/**
* Create a 128 bit UUID in the format: {########-####-####-####-############}
*
*
* Note: this method can be run from the command line as well as from the web server.
* Note2: this method is not cryptographically secure! If you need a cryptographically secure value
* consider using open_ssl or PHP 7 methods.
@@ -1945,26 +2027,9 @@ 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);
}
/**
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
*
@@ -1978,24 +2043,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,14 +2058,9 @@ class utils
*/
public static function GetCurrentModuleUrl()
{
$sDir = static::GetCurrentModuleDir(1);
if ( $sDir !== '')
{
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
}
return '';
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
}
/**
* @param string $sProperty The name of the property to retrieve
* @param mixed $defaultvalue
@@ -2025,24 +2068,18 @@ 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);
}
/**
* @param string $sModuleName
* @return string|NULL compiled version of a given module, as it was seen by the compiler
*/
public static function GetCompiledModuleVersion($sModuleName)
{
$aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
}
/**
* Check if the given path/url is an http(s) URL
* @param string $sPath
@@ -2057,7 +2094,7 @@ class utils
}
return $bRet;
}
/**
* Check if the given URL is a link to download a document/image on the CURRENT iTop
* In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
@@ -2106,7 +2143,7 @@ class utils
}
return $result;
}
/**
* Read the content of a file (and retrieve its MIME type) from either:
* - an URL pointing to a blob (image/document) on the current iTop server
@@ -2150,7 +2187,7 @@ class utils
'html' => 'text/html',
'exe' => 'application/octet-stream',
);
$sData = null;
$sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
$sFileName = 'uploaded-file'; // Default name for downloaded-files
@@ -2206,7 +2243,7 @@ class utils
}
$sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
$sFileName = basename($sPath);
if (array_key_exists($sExtension, $aKnownExtensions))
{
$sMimeType = $aKnownExtensions[$sExtension];
@@ -2220,7 +2257,7 @@ class utils
}
return $oUploadedDoc;
}
protected static function ParseHeaders($aHeaders)
{
$aCleanHeaders = array();
@@ -2245,7 +2282,7 @@ class utils
}
return $aCleanHeaders;
}
/**
* @return string a string based on compilation time or (if not available because the datamodel has not been loaded)
* the version of iTop. This string is useful to prevent browser side caching of content that may vary at each
@@ -2295,6 +2332,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)
*
@@ -2368,7 +2437,7 @@ class utils
{
return false;
}
return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
@@ -2504,4 +2573,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.7');
define('ITOP_CORE_VERSION', '2.7.12');
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)
{
@@ -54,7 +48,7 @@ if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
http_response_code(503);
// Display message depending on the request
include(APPROOT.'application/maintenancemsg.php');
$sSAPIName = strtoupper(trim(php_sapi_name()));
$sSAPIName = strtoupper(trim(PHP_SAPI));
switch (true)
{

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

@@ -328,8 +328,12 @@ class ActionEmail extends ActionNotification
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
$oObj = $aContextArgs['this->object()'];
$sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/),
MetaModel::GetEnvironmentId());
$sMessageId = sprintf('iTop_%s_%d_%F@%s.openitop.org',
get_class($oObj),
$oObj->GetKey(),
microtime(true /* get as float*/),
MetaModel::GetEnvironmentId()
);
$sReference = '<'.$sMessageId.'>';
}
catch (Exception $e) {

View File

@@ -138,7 +138,7 @@ abstract class AttributeDefinition
protected $aCSSClasses;
public function GetType()
public function GetType()
{
return Dict::S('Core:'.get_class($this));
}
@@ -2695,6 +2695,11 @@ class AttributeObjectKey extends AttributeDBFieldVoid
return ($proposedValue == 0);
}
/**
* @inheritDoc
*
* @param int|DBObject $proposedValue Object key or valid ({@see MetaModel::IsValidObject()}) datamodel object
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
@@ -2707,7 +2712,6 @@ class AttributeObjectKey extends AttributeDBFieldVoid
}
if (MetaModel::IsValidObject($proposedValue))
{
/** @var \DBObject $proposedValue */
return $proposedValue->GetKey();
}
@@ -3771,7 +3775,7 @@ class AttributeFinalClass extends AttributeString
*/
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
@@ -3847,7 +3851,7 @@ class AttributePassword extends AttributeString implements iAttributeNoGroupBy
*/
class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
static $sKey = null; // Encryption key used for all encrypted fields
static $sLibrary = null; // Encryption library used for all encrypted fields
@@ -5940,6 +5944,11 @@ class AttributeDateTime extends AttributeDBField
}
}
/**
* @inheritDoc
*
* @param int|string $proposedValue timestamp ({@see DateTime::getTimestamp()) or date as string, following the {@see GetInternalFormat} format.
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
@@ -7655,9 +7664,9 @@ class AttributeBlob extends AttributeDefinition
}
/**
* Users can provide the document from an URL (including an URL on iTop itself)
* for CSV import. Administrators can even provide the path to a local file
* {@inheritDoc}
* {@inheritDoc}
*
* @param string $proposedValue Can be an URL (including an URL to iTop itself), or a local path (CSV import)
*
* @see AttributeDefinition::MakeRealValue()
*/
@@ -9234,7 +9243,7 @@ class AttributeSubItem extends AttributeDefinition
*/
class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)

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

@@ -95,7 +95,7 @@ class MySQLHasGoneAwayException extends MySQLException
{
return array(
2006,
2013
2013,
);
}
@@ -113,10 +113,18 @@ class MySQLNoTransactionException extends MySQLException
}
/**
* @since 2.7.8 3.0.3 3.1.0 N°5538
*/
class MySQLTransactionNotClosedException extends MySQLException
{
}
/**
* CMDBSource
* database access wrapper
* database access wrapper
*
* @package iTopORM
*/
@@ -126,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
@@ -311,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];
}
@@ -338,10 +355,6 @@ class CMDBSource
{
$iPort = (int)($aConnectInfo[1]);
}
else
{
$iPort = 3306;
}
}
/**
@@ -527,6 +540,7 @@ class CMDBSource
{
self::$m_sDBName = '';
}
self::_TablesInfoCacheReset(); // reset the table info cache!
}
public static function CreateTable($sQuery)
@@ -660,10 +674,9 @@ class CMDBSource
/**
* @param string $sSQLQuery
*
* @return \mysqli_result|null
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \CoreException
* @return mysqli_result|null
* @throws MySQLException
* @throws MySQLHasGoneAwayException
*
* @since 2.7.0 N°679 handles nested transactions
*/
@@ -723,8 +736,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);
@@ -1282,8 +1296,8 @@ class CMDBSource
*/
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
{
list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
[$sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
[$sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sDbFieldType);
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
{
@@ -1720,8 +1734,20 @@ class CMDBSource
return false;
}
/**
* @return string query to upgrade database charset and collation if needed, null if not
public static function GetClusterNb()
{
$result = 0;
$sSql = "SHOW STATUS LIKE 'wsrep_cluster_size';";
$aRows = self::QueryToArray($sSql);
if (count($aRows) > 0)
{
$result = $aRows[0]['Value'];
}
return intval($result);
}
/**
* @return string query to upgrade database charset and collation if needed, null if not
* @throws \MySQLException
*
* @since 2.5.0 N°1001 switch to utf8mb4

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)',
@@ -1602,7 +1634,7 @@ class Config
}
if (strlen($sNoise) > 0)
{
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
throw new ConfigException('Syntax error in configuration file',
array('file' => $sConfigFile, 'error' => '<tt>'.htmlentities($sNoise, ENT_QUOTES, 'UTF-8').'</tt>'));
}
@@ -1877,6 +1909,24 @@ class Config
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
}
/**
* @since 2.7.11 N°7085
* Add login mode if not configured already
* @param string $sLoginMode
*
* @return void
*/
public function AddAllowedLoginTypes($sLoginMode)
{
$aAllowedLoginTypes = $this->GetAllowedLoginTypes();
if (in_array($sLoginMode, $aAllowedLoginTypes)){
return;
}
$aAllowedLoginTypes[] = $sLoginMode;
$this->SetAllowedLoginTypes($aAllowedLoginTypes);
}
public function SetExternalAuthenticationVariable($sExtAuthVariable)
{
$this->m_sExtAuthVariable = $sExtAuthVariable;
@@ -2396,7 +2446,7 @@ class ConfigPlaceholdersResolver
}
$sPattern = '/\%(env|server)\((\w+)\)(?:\?:(\w*))?\%/'; //3 capturing groups, ie `%env(HTTP_PORT)?:8080%` produce: `env` `HTTP_PORT` and `8080`.
if (! preg_match_all($sPattern, $rawValue, $aMatchesCollection, PREG_SET_ORDER))
{
return $rawValue;
@@ -2449,4 +2499,4 @@ class ConfigPlaceholdersResolver
IssueLog::Error($sErrorMessage, self::class, array($sSourceName, $sKey, $sDefault, $sWholeMask));
throw new ConfigException($sErrorMessage);
}
}
}

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

@@ -532,11 +532,10 @@ abstract class DBObject implements iDisplay
* Attributes setter
*
* Set $sAttCode to $value.
* The value must be valid according to the type of attribute.
* The value must be valid according to the type of attribute : see the different {@see AttributeDefinition::MakeRealValue()} implementations
* The value will not be recorded into the DB until DBObject::DBWrite() is called.
*
* @api
* @see DBWrite()
*
* @param string $sAttCode
* @param mixed $value
@@ -544,6 +543,8 @@ abstract class DBObject implements iDisplay
* @return bool
* @throws CoreException
* @throws CoreUnexpectedValue
*
* @see DBWrite()
*/
public function Set($sAttCode, $value)
{
@@ -2084,16 +2085,17 @@ abstract class DBObject implements iDisplay
}
/**
* @param array $aUniquenessRuleProperties uniqueness rule properties
*
* @param string $sUniquenessRuleId uniqueness rule ID
* @return \DBSearch
* @throws \OQLException
* @throws \CoreException
*
* @internal
*
* @param string $sUniquenessRuleId uniqueness rule ID
* @param array $aUniquenessRuleProperties uniqueness rule properties
*
* @return \DBSearch
* @throws \CoreException
* @throws \OQLException
* @since 2.6.0 N°659 uniqueness constraint
* @api
* @since 2.6.0 N°659 uniqueness constraint
* @since 2.7.11 3.1.2 3.2.0 N°4314 Fix Uniqueness rules not working with Silo
*/
protected function GetSearchForUniquenessRule($sUniquenessRuleId, $aUniquenessRuleProperties)
{
@@ -2123,8 +2125,10 @@ abstract class DBObject implements iDisplay
$oUniquenessQuery->AddConditionForInOperatorUsingParam('finalclass', $aChildClassesWithRuleDisabled, false);
}
return $oUniquenessQuery;
}
$oUniquenessQuery->AllowAllData();
return $oUniquenessQuery;
}
/**
* Check integrity rules (before inserting or updating the object)
@@ -2140,7 +2144,6 @@ abstract class DBObject implements iDisplay
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
*/
public function DoCheckToWrite()
{
@@ -2195,7 +2198,6 @@ abstract class DBObject implements iDisplay
}
/**
*
* @api
* @api-advanced
*
@@ -2211,7 +2213,6 @@ abstract class DBObject implements iDisplay
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
*/
final public function CheckToWrite()
{
@@ -2225,7 +2226,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 +2239,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 +2465,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 +2762,6 @@ abstract class DBObject implements iDisplay
* @throws \Exception
*
* @internal
*
*/
public function DBInsertNoReload()
{
@@ -2693,8 +2774,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 +2797,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 +2903,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 +2913,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 +3041,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 +3050,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 +3154,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 +3168,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 +3182,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 +3205,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 +3219,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 +3244,7 @@ abstract class DBObject implements iDisplay
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
@@ -3324,7 +3419,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 +3500,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 +3572,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 +3993,7 @@ abstract class DBObject implements iDisplay
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
@@ -3902,6 +4005,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))
{
@@ -866,11 +866,11 @@ abstract class DBSearch
return;
}
if (count($aColumns) == 0)
{
$aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass()));
// Add the standard id (as first column)
array_unshift($aColumns, 'id');
if (count($aColumns) == 0)
{
$aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass()));
// Add the standard id (as first column)
array_unshift($aColumns, 'id');
}
$aQueryCols = CMDBSource::GetColumns($resQuery, $sSQL);
@@ -900,6 +900,55 @@ abstract class DBSearch
return $aRes;
}
/**
* Selects a column ($sAttCode) from the specified class ($sClassAlias - default main class) of the DBsearch object and gives the result as an array
* @param string $sAttCode
* @param string|null $sClassAlias
*
* @return array
* @throws ConfigException
* @throws CoreException
* @throws MissingQueryArgument
* @throws MySQLException
* @throws MySQLHasGoneAwayException
*/
public function SelectAttributeToArray(string $sAttCode, ?string $sClassAlias = null):array
{
if(is_null($sClassAlias)) {
$sClassAlias = $this->GetClassAlias();
}
$sClass = $this->GetClass();
if($sAttCode === 'id'){
$aAttToLoad[$sClassAlias]=[];
} else {
$aAttToLoad[$sClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sClass, $sAttCode);
}
$sSQL = $this->MakeSelectQuery([], [], $aAttToLoad);
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery)
{
return [];
}
$sColName = $sClassAlias.$sAttCode;
$aRes = [];
while ($aRow = CMDBSource::FetchArray($resQuery))
{
$aMappedRow = array();
if($sAttCode === 'id') {
$aMappedRow[$sAttCode] = $aRow[$sColName];
} else {
$aMappedRow[$sAttCode] = $aAttToLoad[$sClassAlias][$sAttCode]->FromSQLToValue($aRow, $sColName);
}
$aRes[] = $aMappedRow;
}
CMDBSource::FreeResult($resQuery);
return $aRes;
}
////////////////////////////////////////////////////////////////////////////
//
// Construction of the SQL queries

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

@@ -34,6 +34,13 @@ define ('EMAIL_SEND_ERROR', 2);
class EMail implements iEMail
{
/**
* @see self::LoadConfig()
* @var Config
* @since 2.7.7 3.0.2 3.1.0 N°3169 N°5102 Move attribute to children classes
* @since 2.7.8 3.0.3 3.1.0 N°4947 pull up the attribute back to the Email class as config init is done here
*/
protected static $m_oConfig = null;
protected $oMailer;
// Serialization formats
@@ -46,6 +53,42 @@ class EMail implements iEMail
$this->oMailer = EmailFactory::GetMailer();
}
/**
* Sets {@see m_oConfig} if current attribute is null
*
* @returns \Config the current {@see m_oConfig} value
* @throws \ConfigException
* @throws \CoreException
*
* @uses utils::GetConfig()
*
* @since 2.7.7 3.0.2 3.1.0 N°3169 N°5102 Move method to children classes
* @since 2.7.8 3.0.3 3.1.0 N°4947 Pull up to the parent class, and remove `$sConfigFile` param
*/
public function LoadConfig()
{
if (is_null(static::$m_oConfig)) {
static::$m_oConfig = utils::GetConfig();
}
return static::$m_oConfig;
}
/**
* @return void
* @throws \ConfigException
* @throws \CoreException
* @since 2.7.8 3.0.3 3.1.0 N°4947 Method creation, to factorize same code in children classes
*/
protected function InitRecipientFrom()
{
$oConfig = $this->LoadConfig();
$this->SetRecipientFrom(
$oConfig->Get('email_default_sender_address'),
$oConfig->Get('email_default_sender_label')
);
}
/**
* Custom serialization method
* No longer use the brute force "serialize" method since
@@ -146,5 +189,4 @@ class EMail implements iEMail
{
$this->oMailer->SetRecipientReplyTo($sAddress);
}
}

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;
@@ -7344,14 +7366,11 @@ abstract class MetaModel
$aSearches = array();
$aReplacements = array();
foreach ($aParams as $sSearch => $replace)
{
foreach ($aParams as $sSearch => $replace) {
// Some environment parameters are objects, we just need scalars
if (is_object($replace))
{
if (is_object($replace)) {
$iPos = strpos($sSearch, '->object()');
if ($iPos !== false)
{
if ($iPos !== false) {
// Expand the parameters for the object
$sName = substr($sSearch, 0, $iPos);
// Note: Capturing
@@ -7359,63 +7378,67 @@ abstract class MetaModel
// 2 - The arrow
// 3 - The attribute code
$aRegExps = array(
'/(\\$)'.$sName.'-(>|&gt;)([^\\$]+)\\$/', // Support both syntaxes: $this->xxx$ or $this-&gt;xxx$ for HTML compatibility
'/(%24)'.$sName.'-(>|&gt;)([^%24]+)%24/', // Support for urlencoded in HTML attributes (%20this-&gt;xxx%20)
);
foreach($aRegExps as $sRegExp)
{
if(preg_match_all($sRegExp, $sInput, $aMatches))
{
foreach($aMatches[3] as $idx => $sPlaceholderAttCode)
{
try
{
$sReplacement = $replace->GetForTemplate($sPlaceholderAttCode);
if($sReplacement !== null)
{
$aReplacements[] = $sReplacement;
$aSearches[] = $aMatches[1][$idx] . $sName . '-' . $aMatches[2][$idx] . $sPlaceholderAttCode . $aMatches[1][$idx];
}
}
catch(Exception $e)
{
// No replacement will occur
}
}
}
}
}
else
{
'/(\\$)'.$sName.'-(>|&gt;)([^\\$]+)\\$/', // Support both syntaxes: $this->xxx$ or $this-&gt;xxx$ for HTML compatibility
'/(%24)'.$sName.'-(>|&gt;)([^%24]+)%24/', // Support for urlencoded in HTML attributes (%20this-&gt;xxx%20)
);
foreach ($aRegExps as $sRegExp) {
if (preg_match_all($sRegExp, $sInput, $aMatches)) {
foreach ($aMatches[3] as $idx => $sPlaceholderAttCode) {
try {
$sReplacement = $replace->GetForTemplate($sPlaceholderAttCode);
if ($sReplacement !== null) {
$aReplacements[] = $sReplacement;
$aSearches[] = $aMatches[1][$idx].$sName.'-'.$aMatches[2][$idx].$sPlaceholderAttCode.$aMatches[1][$idx];
}
}
catch (Exception $e) {
$aContext = [
'placeholder' => $sPlaceholderAttCode,
'replace class' => get_class($replace),
];
if ($replace instanceof DBObject) {
$aContext['replace id'] = $replace->GetKey();
}
IssueLog::Debug(
'Invalid placeholder in notification, no replacement will occur!',
LogChannels::NOTIFICATIONS,
$aContext
);
}
}
}
}
} else {
continue; // Ignore this non-scalar value
}
}
else
{
} else {
$aRegExps = array(
'/(\$)'.$sSearch.'\$/', // Support for regular placeholders (eg. $APP_URL$)
'/(%24)'.$sSearch.'%24/', // Support for urlencoded in HTML attributes (eg. %24APP_URL%24)
);
foreach($aRegExps as $sRegExp)
{
if(preg_match_all($sRegExp, $sInput, $aMatches))
{
foreach($aMatches[1] as $idx => $sDelimiter)
{
try
{
$aReplacements[] = (string) $replace;
$aSearches[] = $aMatches[1][$idx] . $sSearch . $aMatches[1][$idx];
foreach ($aRegExps as $sRegExp) {
if (preg_match_all($sRegExp, $sInput, $aMatches)) {
foreach ($aMatches[1] as $idx => $sDelimiter) {
try {
$aReplacements[] = (string)$replace;
$aSearches[] = $aMatches[1][$idx].$sSearch.$aMatches[1][$idx];
}
catch(Exception $e)
{
// No replacement will occur
catch (Exception $e) {
IssueLog::Debug(
'Invalid placeholder in notification, no replacement will occur !',
LogChannels::NOTIFICATIONS,
[
'placeholder' => $sPlaceholderAttCode,
'replace' => $replace,
]
);
}
}
}
}
}
}
return str_replace($aSearches, $aReplacements, $sInput);
}

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

@@ -34,7 +34,9 @@
*/
class ObjectResult
{
public $code;
use SanitizeTrait;
public $code;
public $message;
public $class;
public $key;
@@ -122,6 +124,19 @@ class ObjectResult
{
$this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode, $bExtendedOutput);
}
public function SanitizeContent()
{
foreach($this->fields as $sFieldAttCode => $fieldValue)
{
try {
$oAttDef = MetaModel::GetAttributeDef($this->class, $sFieldAttCode);
} catch (Exception $e) { // for special cases like ID
continue;
}
$this->SanitizeFieldIfSensitive($this->fields, $sFieldAttCode, $fieldValue, $oAttDef);
}
}
}
@@ -129,7 +144,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
*/
@@ -181,6 +196,16 @@ class RestResultWithObjects extends RestResult
$sObjKey = get_class($oObject).'::'.$oObject->GetKey();
$this->objects[$sObjKey] = $oObjRes;
}
public function SanitizeContent()
{
parent::SanitizeContent();
foreach($this->objects as $sObjKey => $oObjRes)
{
$oObjRes->SanitizeContent();
}
}
}
class RestResultWithRelations extends RestResultWithObjects
@@ -206,7 +231,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
*/
@@ -247,9 +272,10 @@ class RestDelete
*
* @package Core
*/
class CoreServices implements iRestServiceProvider
class CoreServices implements iRestServiceProvider, iRestInputSanitizer
{
/**
use SanitizeTrait;
/**
* Enumerate services delivered by this class
*
* @param string $sVersion The version (e.g. 1.0) supported by the services
@@ -663,6 +689,33 @@ class CoreServices implements iRestServiceProvider
}
return $oResult;
}
public function SanitizeJsonInput(string $sJsonInput): string
{
$sSanitizedJsonInput = $sJsonInput;
$aJsonData = json_decode($sSanitizedJsonInput, true);
$sOperation = $aJsonData['operation'];
switch ($sOperation) {
case 'core/check_credentials':
if (isset($aJsonData['password'])) {
$aJsonData['password'] = '*****';
}
break;
case 'core/update':
case 'core/create':
default :
$sClass = $aJsonData['class'];
if (isset($aJsonData['fields'])) {
foreach ($aJsonData['fields'] as $sFieldAttCode => $fieldValue) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sFieldAttCode);
$this->SanitizeFieldIfSensitive($aJsonData['fields'], $sFieldAttCode, $fieldValue, $oAttDef);
}
}
break;
}
return json_encode($aJsonData, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
/**
* Helper for object deletion
@@ -802,3 +855,50 @@ class CoreServices implements iRestServiceProvider
return $iLimit * max(0, $iPage - 1);
}
}
trait SanitizeTrait
{
/**
* Sanitize a field if it is sensitive.
*
* @param array $fields The fields array
* @param string $sFieldAttCode The attribute code
* @param mixed $oAttDef The attribute definition
* @throws Exception
*/
private function SanitizeFieldIfSensitive(array &$fields, string $sFieldAttCode, $fieldValue, $oAttDef): void
{
// for simple attribute
if ($oAttDef instanceof iAttributeNoGroupBy) // iAttributeNoGroupBy is equivalent to sensitive attribute
{
$fields[$sFieldAttCode] = '*****';
return;
}
// for 1-n / n-n relation
if ($oAttDef instanceof AttributeLinkedSet) {
foreach ($fieldValue as $i => $aLnkValues) {
foreach ($aLnkValues as $sLnkAttCode => $sLnkValue) {
$oLnkAttDef = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sLnkAttCode);
if ($oLnkAttDef instanceof iAttributeNoGroupBy) { // 1-n relation
$fields[$sFieldAttCode][$i][$sLnkAttCode] = '*****';
}
elseif ($oAttDef instanceof AttributeLinkedSetIndirect && $oLnkAttDef instanceof AttributeExternalField) { // for n-n relation
$oExtKeyAttDef = MetaModel::GetAttributeDef($oLnkAttDef->GetTargetClass(), $oLnkAttDef->GetExtAttCode());
if ($oExtKeyAttDef instanceof iAttributeNoGroupBy) {
$fields[$sFieldAttCode][$i][$sLnkAttCode] = '*****';
}
}
}
}
return;
}
// for external attribute
if ($oAttDef instanceof AttributeExternalField) {
$oExtKeyAttDef = MetaModel::GetAttributeDef($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode());
if ($oExtKeyAttDef instanceof iAttributeNoGroupBy) {
$fields[$sFieldAttCode] = '*****';
}
}
}
}

View File

@@ -537,7 +537,7 @@ EOF
}
else
{
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
throw new Exception('graphviz not found');
}
return $sHtml;
}
@@ -592,7 +592,7 @@ EOF
}
else
{
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
throw new Exception('graphviz not found');
}
return $sHtml;
}

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

@@ -110,7 +110,7 @@ abstract class UserRightsAddOnAPI
$oSearchSharers->AllowAllData();
$oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id');
$aSharers = array();
foreach($oSearchSharers->ToDataArray(array('id')) as $aRow)
foreach($oSearchSharers->SelectAttributeToArray('id') as $aRow)
{
$aSharers[] = $aRow['id'];
}
@@ -135,7 +135,7 @@ abstract class UserRightsAddOnAPI
$oOrgField = new FieldExpression('org_id', $sShareClass);
$oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$aShared = array();
foreach($oSearchShares->ToDataArray(array($sShareAttCode)) as $aRow)
foreach($oSearchShares->SelectAttributeToArray($sShareAttCode) as $aRow)
{
$aShared[] = $aRow[$sShareAttCode];
}
@@ -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.7";
$version: "v2.7.12";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors

View File

@@ -15,10 +15,14 @@
*
* You should have received a copy of the GNU Affero General Public License
*/
/* integrityCheck: begin (do not remove/edit) */
/* Helpers classes */
.center {
text-align: center;
}
.hidden {
display: none;
}
/* Animations */
@keyframes progress_bar_color_ongoing {
from {
@@ -265,4 +269,4 @@ fieldset > legend {
font-weight: bold;
color: #e60000b8;
}
/* integrityCheck: end (do not remove/edit) */

View File

@@ -56,6 +56,9 @@ $progress-bar-error-bg-color: #F56565 !default;
.center {
text-align: center;
}
.hidden {
display: none;
}
/* Animations */
@keyframes progress_bar_color_ongoing {

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.7',
'authent-cas/2.7.12',
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

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