Compare commits

..

80 Commits

Author SHA1 Message Date
Eric Espie
4ada74e63f Debug data within the form 2025-10-22 17:37:38 +02:00
Eric Espie
0ccb452ab7 merge 2025-10-22 16:55:09 +02:00
Benjamin Dalsass
600a6185a3 N°8771 - Add Symfony form component to iTop core
- restore $aAdditionalPaths missing parameter
2025-10-22 11:30:48 +02:00
Eric Espie
354a7cabab Merge branch 'feature/8781_-_Improve_twig_base_controller_render_error_report' into develop 2025-10-21 13:58:45 +02:00
Eric Espie
0802e5abb5 N°8781 - Improve twig base controller render error report 2025-10-21 13:57:35 +02:00
odain
8ade0a6e85 N°4058 - fix PHP Deprecated base64_decodestrlen : Passing nul 2025-10-21 08:09:48 +02:00
Benjamin Dalsass
0dae7346d1 N°8772 - Form dependencies manager implementation
- turbo implementation
2025-10-20 15:16:44 +02:00
Benjamin Dalsass
cdfded766f N°8772 - Form dependencies manager implementation 2025-10-17 09:03:45 +02:00
Stephen Abello
bfc583e6b5 N°8701 - Fix class icon alt text displaying in portal tile when there's no icon defined 2025-10-15 15:18:03 +02:00
Benjamin Dalsass
5bc453bca6 N°8755 - Remove unused Symfony features for iTop 3.3 2025-10-13 08:24:20 +02:00
Benjamin Dalsass
5dd450e9bf N°8771 - Add Symfony form component to iTop core (#760)
- Add Symfony Form Component
- Add Symfony CSRF security component
- Add iTop default form template
- Add Twig debug extension to Twig Environment
- Add iTop abstract controller facility to get form builder
- Add Twig filter to make trans an alias of dict_s filter
2025-10-10 16:02:25 +02:00
Anne-Cath
82395727bf N°8699 - add forgotten 'use' 2025-10-10 15:48:05 +02:00
odain-cbd
318b792b31 N°4058 - Setup failed when added an encrypted field due to default value NULL non SODIUM compatible (#754)
* N°4058 - Setup failed when added an encrypted field due to default value NULL non SODIUM compatible

* N°4058 - Setup failed when added an encrypted field due to default value NULL non SODIUM compatible

* change log level to warning
2025-10-10 14:25:39 +02:00
jf-cbd
0fe2183369 Update itop-version-history.md 2025-10-07 15:19:08 +02:00
Anne-Catherine
8f038d2f95 N°2683 - EnhancedSLAComputation: use common extension mechanism (#744) 2025-10-02 15:40:16 +02:00
jf-cbd
fbf3dd8cf7 📝 Update CONTRIBUTING.md for Hacktoberfest 2025-10-01 11:34:28 +02:00
jf-cbd
01c9e73020 Improve OQLTest.php so data produced by test (added ub N°8522) is removed at the end of its execution 2025-09-30 16:46:58 +02:00
jf-cbd
0736045b90 Update .doc 2025-09-30 14:53:25 +02:00
Eduardo Mozart de Oliveira
86776edfb3 Improved PT-BR Translation (#362) 2025-09-29 11:01:25 +02:00
jf-cbd
d78805d8ae 👥 Update README.md 2025-09-22 17:10:59 +02:00
Håkon Harnes
03e1d46586 🐛 N°8522 - check if org id is null when filtering (#727) 2025-09-22 16:51:32 +02:00
Eric Espie
f3deb8be11 Merge remote-tracking branch 'origin/support/3.2' into develop 2025-09-22 16:44:22 +02:00
Eric Espie
981d5c6263 Allow KPIs for REST api 2025-09-22 16:43:24 +02:00
Stephen Abello
e000befc0f Dump autoloader 2025-09-22 16:26:12 +02:00
Stephen Abello
2eba58998b Merge branch 'support/3.2' into develop 2025-09-22 16:25:27 +02:00
Stephen Abello
4ed21dc21a N°7920 - Replace Symfony sendmail transport with PHP mail() transport 2025-09-22 16:24:58 +02:00
Anne-Cath
fa5cad0fdb N°2364 - API : remove old linkedset persistance - avoid deprecated message during setup 2025-09-22 12:02:28 +02:00
Thomas Casteleyn
5d28de1636 📝 Complete the version history (#753) 2025-09-19 17:22:18 +02:00
Timmy38
6be9255ca0 N°7071 - Remove CMDBSource::MYSQL_DEFAULT_PORT 2025-09-19 10:53:58 +02:00
Stephen Abello
1f4a2f0f56 N°8579 - Fix calls to Twig "spaceless" deprecated filter 2025-09-18 10:35:02 +02:00
Stephen Abello
edbe4974ac Re-dump autoloader and composer.lock 2025-09-18 10:26:38 +02:00
Stephen Abello
7e515e7216 Merge branch 'support/3.2' into develop 2025-09-18 10:13:09 +02:00
Stephen Abello
bf7a756714 Add symfony/mailer/Test to denied dirs 2025-09-17 16:25:22 +02:00
Stephen Abello
428d2c6356 N°7920 - laminas-mail is an abandoned package, replace it with symfony/mailer (#742)
* N°7920 - laminas-mail is an abandoned package, replace it with symfony/mailer

* Fix composer following merge
2025-09-17 16:04:20 +02:00
odain
d589d9d05a N°4789 - fix broken tests in ci 2025-09-16 16:37:31 +02:00
odain
c0c9ea9287 N°4789 - Parse datamodel module.xxx.php files instead of interpreting them (#746) - namespacing ModuleFileReader classes 2025-09-16 15:38:30 +02:00
odain
fae2bcc6e9 N°4720 - deprecation removal RenderLinks 2025-09-15 16:40:10 +02:00
Stephen Abello
53047d35fe N°8617 - New portal theme doesn't work with Safari 2025-09-15 14:30:07 +02:00
Stephen Abello
8982f7e0e3 Dump autoloader 2025-09-15 14:16:03 +02:00
Anne-Catherine
909469ce97 N°2364 - API : remove old linkedset persistance (#733) 2025-09-12 16:16:02 +02:00
Anne-Cath
dacb54285c N°4820 - Remove pre-3.0 console files - date.js, hovertip.js jquery.layout.min.js 2025-09-12 16:01:55 +02:00
Benjamin Dalsass
5af93ca92a N°8637 - Alerts from dependabot, vulnerable libraries
- remove deprecated call to getExpressionParser
2025-09-12 15:38:01 +02:00
Benjamin Dalsass
3fa500c9c1 N°8699 - attributedef.class.inc.php to PSR4 [3-PSR4]
- Add namespaces
- Add use statements
- reformat code
2025-09-12 15:23:42 +02:00
Benjamin Dalsass
f0adbbba29 N°8699 - attributedef.class.inc.php to PSR4 [2-DISPATCH]
- Restore attributedef.class.inc.php with requires
- Update each attribute file code
2025-09-12 15:23:42 +02:00
Benjamin Dalsass
bbdb30f421 N°8699 - attributedef.class.inc.php to PSR4 [1-DUPLICATE]
- Duplicates attributedef.class.inc.php as attributedefrequires.class.inc.php (will contains the require_once directives)
- Duplicates attributedef.class.inc.php for each final class files AttributeApplicationLanguage.php, AttributeArchiveDate.php... to keep VCS
 history
- Remove attributedef.class.inc.php file
2025-09-12 15:23:42 +02:00
odain
897b5d452e N°8715 - Broken UIFieldSet/UIFieldInput via twig rendering 2025-09-12 15:00:40 +02:00
odain
fdd1479c8e N°4720 - fix portal issue 2025-09-12 14:36:19 +02:00
Anne-Cath
7851109794 N°3749 - Remove old JS tooltip libs (qTip, jQuery tooltip, Bootstrap tooltip) calls - remove qtip 2025-09-12 10:02:12 +02:00
Anne-Cath
810e6c1bda N°3817 - Audit and fix calls to deprecated jQuery method 2025-09-12 09:59:02 +02:00
Anne-Cath
d164f48cfe N°3817 - Audit and fix calls to deprecated jQuery method 2025-09-12 09:56:30 +02:00
odain
506def4340 code cleanup : fix PHP Warning: Undefined variable 2025-09-12 09:41:03 +02:00
odain
08b8e919b7 N°4720 - Remove old unreferenced methods - portal broken due to remaining method in portal-base 2025-09-12 07:19:11 +02:00
odain-cbd
696e2c9565 N°4720 - Remove old unreferenced methods or pages that are @deprecated (#747)
* N°4720 - Remove old unreferenced methods that are @deprecated

* N°4720-Config addson getter/setter removal

* small deprecation removal

* revert @deprecation 3.3 removal: ResizeImageToFit

* small deprecation removal

* revert deprecation 3.3 removal: Config/GSetAddons
2025-09-11 15:52:00 +02:00
Anne-Cath
d728be119c N°4456 - Remove jquery.layout.js 2025-09-11 15:27:57 +02:00
Benjamin Dalsass
463869b89d N°8637 - Alerts from dependabot, vulnerable libraries
- restore itop version history
2025-09-11 11:03:58 +02:00
Benjamin Dalsass
1d4c0b8e7f N°8637 - Alerts from dependabot, vulnerable libraries
- Update twig/twig from 3.16.0 to 3.21.1
- Update tecnickcom/tcpdf from 6.7.5 to 6.10.0
2025-09-10 09:08:04 +02:00
Benjamin Dalsass
bf1b2a5104 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	.doc/README.md
#	.doc/phpdoc-templates/combodo-wiki/graphs/class.html.twig
#	composer.lock
#	lib/composer/InstalledVersions.php
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_psr4.php
#	lib/composer/autoload_static.php
#	lib/composer/installed.json
#	lib/composer/installed.php
#	lib/symfony/cache/Traits/Relay/MoveTrait.php
#	lib/symfony/http-foundation/Request.php
#	lib/symfony/runtime/SymfonyRuntime.php
2025-09-10 07:57:41 +02:00
odain-cbd
15103dc49f N°4789 - Parse datamodel module.xxx.php files instead of interpreting them (#746)
* N°4789 - Parse datamodel module.xxx.php files instead of interpreting them - refactoring all in a dedicated service first

* N°4789 - fix broken setup + tests

* N°4789 - replace legacy eval by module file parsing

* N°4789 - handle constants and if conditional structures

* N°4789 - compute boolean expressions

* N°4789 - make autoselect and dependencies work as well

* cleanup

* N°4789 - fix BeforeWritingConfig calls during setup

* N°4789 - refactor and split in ModuleDiscoveryEvaluationService + handle ModuleInstallerAPI methods calls during setup

* N°4789 - PR review changes with Romain

* PR review + code cleanup + added usecases and test cover

* temp evaluation work

* replace eval by iTop custom evaluation classes

* move PhpParser/Evaluation classes in a specific namespave + composer dumpautoload

* fix broken setup

* fix broken setup

* complete Evaluators list + autoload

* cleanup useless testing resources

* cleanup + replace last eval call in VariableEvaluator

* fix few Evaluators code

* enhance nikic evaluators + test with/without nikic lib

* Evaluator fixes/enhancements + tests

* bump to nikic fork temporarly

* bump nikic-parser fork + use only nikic fork  evaluation + cleanup itop redondant evaluators

* review with Romain: use distinct whitelists in setup time/runtime + move ModuleFileParser internal logic into ModuleFileReader

* PhpExpressionEvaluator used via constructor and not as a service

* dumpautoload again after rebase
2025-09-09 17:54:18 +02:00
Benjamin Dalsass
bb8a09d8e2 N°8637 - Alerts from dependabot, vulnerable libraries
* Update twig/twig from 3.16.0 to 3.21.1

* Update tecnickcom/tcpdf from 6.7.5 to 6.10.0

* Correct font folder case failing on linux server

* Suppress documentation generator from project in favor of the online version

* Update symfony/http-foundation from 6.4.2 to 6.4.14
Update symfony/runtime from 6.4.0 to 6.4.24
2025-09-09 16:15:21 +02:00
Stephen Abello
901f8f2a7b N°8691 - Backup modal icon spins around the text 2025-09-09 14:24:18 +02:00
Stephen Abello
ba6fff801b N°8497 - Fix typo in French dictionaries 2025-09-08 14:25:54 +02:00
Stephen Abello
50098bad73 N°5200 - Activity panel entries' relative date isn't translated 2025-09-08 14:03:43 +02:00
jf-cbd
2ee68ff819 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	sources/SessionTracker/iSessionHandlerExtension.php
2025-09-05 15:19:38 +02:00
Benjamin Dalsass
44972f34e5 N°8210 - Remove iApplicationObjectExtension (#737) 2025-09-05 09:27:55 +02:00
jf-cbd
65c9145b10 N°8570 - Allow @ as part of url - add test 2025-09-04 17:04:21 +02:00
Stephen Abello
a104310379 N°8579 - Fix calls to Twig "spaceless" deprecated filter 2025-09-04 14:57:24 +02:00
Anne-Cath
f5cce23bb4 Revert "N°8579 - remove "spaceless" in twig files"
This reverts commit ee993ef80a.
2025-09-03 10:37:26 +02:00
Stephen Abello
0f39106b56 N°7761 - De-hardcode SCSS values 2025-09-02 16:30:20 +02:00
Anne-Cath
ee993ef80a N°8579 - remove "spaceless" in twig files 2025-08-29 17:15:47 +02:00
Anne-Cath
0a4180f7fc N°7257 - Check and migrate old Combodo JS functions 2025-08-29 16:15:31 +02:00
Anne-Cath
b56113aada N°8190 - Userrights management override is deprecated - add deprecated 2025-08-29 15:12:36 +02:00
Anne-Cath
c82c150411 N°7257 - Check and migrate old Combodo JS functions 2025-08-29 14:27:38 +02:00
Anne-Cath
00818f411f N°3817 - Audit and fix calls to deprecated jQuery method - rollback change on caselog-entry-form.js 2025-08-29 11:07:44 +02:00
Timmy38
a6a459967e N°6759 factorize config (#738)
* N°6759 - Factorize code in config
2025-08-28 11:04:49 +02:00
Stephen Abello
437296eab9 N°8634 - Newsroom providers are hardly visible if there's more than 2 2025-08-20 16:00:14 +02:00
Stephen Abello
7cf6f65265 Fix typo thanks to @jbostoen 2025-08-06 09:04:36 +02:00
Björn Rudner
491f55c7bd 🐛 Allow @ as part of url (#729) 2025-08-01 15:27:37 +02:00
Stephen Abello
7b44ec23a1 Fix return type in PHPDoc thanks to @jbostoen 2025-07-28 16:14:13 +02:00
BenGrenoble
f00b8d529c N°8286 - Be able to add additional data in iTop session files via custom classes
Change to fit convention.
2025-07-24 10:59:48 +02:00
BenGrenoble
af44ffd0ad N°8286 - Be able to add additional data in iTop session files via custom classes
Experimental warning
2025-07-24 10:28:15 +02:00
2511 changed files with 116952 additions and 128386 deletions

View File

@@ -1,101 +0,0 @@
# Phpdoc dokuwiki template
This directory contains a template for rendering iTop phpdoc as dokuwiki pages.
Conventional tags that you should use:
* `@internal` : exclude from the documentation.
* `@api` : it means that a method is an api, thus it may be interacted with.
* `@see` : it points to another documented method
* `@link` : external url
* if you point to another page of the wiki, please use relative links.
* `@example` : let you provide example of code
* `@param`, `@return`, `@throws`, ...
## Special instructions
Some iTop specific tags were added :
* `@api-advanced`: it means that a method is an `@api` but mark it also as "complex" to use
* `@overwritable-hook`: used to mark a method as "designed to be extended"
* `@extension-hook`: not used for now
* `@phpdoc-tuning-exclude-inherited`: once this tag is present on a class, it's inherited methods won't be showed.
### known limitations:
#### `@see` tags must be very specific:
* always prefix class members (attributes or methods) with `ClassName::` (do not use self)
* for methods always suffix them with `()`,
* do not reference variables since they are not documented. If you have to, always prefix them with `$`
examples:
```
/**
* @see DBObject
* @see DBObject::Get()
* @see DBObject::$foo
*/
```
#### Do not use inline tags, they do not work properly, example:
```
/**
* This is a texts with an inline tag {@see [FQSEN] [<description>]} it must never be used
*/
```
#### The `@example` tag must respect this very precise syntax
* the sentence in the first line (next to the tag) is the title, it must be enclosed by double quotes
* the following lines are the sample code.
* 💔 since we simply hack the official tag, this syntax must be respected carefully 💔
example:
```
/**
* @example "This is the title of the multiline example"
* $foo = DBObject::Get('foo');
* DBObject::Set('foo', ++$foo);
*/
```
## How content is included into the documentation
**For a class** those requirements have to be respected:
- the file containing the class must be listed in `/phpdoc/files/file[]` of `.doc/phpdoc-objects-manipulation.dist.xml`
- the class **must not** have the tag `@internal`
- the class **must** have at least one of: `@api`, `@api-advanced`, `@overwritable-hook`, `@extension-hook`
Then, **for a method** of an eligible class:
- **public** methods **must** have at least one of: `@api`, `@api-advanced`, `@overwritable-hook`, `@extension-hook`
- **protected** methods **must** have at least one of: `@overwritable-hook`, `@extension-hook`
- **private** methods are **always excluded**
**Class properties** and **constants** are never documented (this is subject to change).
## A note about the rendering engine
:notebook: as spaces are used to mark code, the templates (`.doc/phpdoc-templates/combodo-wiki/*`) have very few indentation, thus they are awful to read (sorry).
## Installation
Note : PHP7 is required. Migrating to PHP8 requires some additional work which is questionable as an alternative way to generate a documentation is being considered.
```
cd .doc
composer require phpdocumentor/phpdocumentor:~2 --dev
```
## Generation
1. Switch to this directory : `cd /path/to/itop/.doc`
2. `composer install`
3. `./bin/build-doc-object-manipulation`
3. `./bin/build-doc-extensions`
4. Get the generated files from `/path/to/itop/data/phpdocumentor/output`
## Dokuwiki requirements
* the template uses the [wrap plugin](https://www.dokuwiki.org/plugin:wrap).
* the generated files have to be placed under an arbitrary directory of `[/path/to/dokuwiki]/data/pages`.
* the html has to be activated [config:htmlok](https://www.dokuwiki.org/config:htmlok)
* the generated files have to be in lowercase

View File

@@ -1,6 +0,0 @@
#!/bin/sh -x
rm -rf /tmp/phpdoc-twig-cache/ && rm -rf data/phpdocumentor/output/extensions/ && rm -rf data/phpdocumentor/temp/extensions/ && ./vendor/bin/phpdoc -c ./phpdoc-extensions.dist.xml -vvv
# now wee need to lowercase every generated file because dokuwiki can't handle uppercase
cd ../data/phpdocumentor/output/extensions/ && for i in $(ls | grep [A-Z]); do mv -i $i $(echo $i | tr 'A-Z' 'a-z'); done

View File

@@ -1,7 +0,0 @@
#!/bin/sh -x
rm -rf /tmp/phpdoc-twig-cache/ && rm -rf ../data/phpdocumentor/output/objects-manipulation/ && rm -rf ../data/phpdocumentor/temp/objects-manipulation/ && ./vendor/bin/phpdoc -c ./phpdoc-objects-manipulation.dist.xml -vvv
# now wee need to lowercase every generated file because dokuwiki can't handle uppercase
cd ../data/phpdocumentor/output/objects-manipulation/ && for i in $( ls | grep [A-Z] ); do mv -i $i `echo $i | tr 'A-Z' 'a-z'`; done

View File

@@ -1,6 +0,0 @@
{
"require-dev": {
"phpdocumentor/phpdocumentor": "~2",
"jms/serializer": "1.7.*"
}
}

3015
.doc/composer.lock generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -86,19 +86,24 @@ gitGraph
commit id: "2024-01-17a" tag: "2.7.10"
checkout support/3.0
commit id: "2024-01-17b" tag: "3.0.4"
checkout support/2.7
commit id: "2024-09-28" tag: "2.7.11"
checkout support/3.1
commit id: "2024-09-27" tag: "3.1.2"
checkout support/3.2
commit id: "2024-06-25" tag: "3.2.0-beta1" type: REVERSE
commit id: "2024-08-07" tag: "3.2.0"
commit id: "2024-09-13" tag: "3.2.0-2"
checkout support/3.1
commit id: "2024-09-27" tag: "3.1.2"
checkout support/2.7
commit id: "2024-09-28" tag: "2.7.11"
checkout support/2.7
commit id: "2025-02-25" tag: "2.7.12"
checkout support/3.1
commit id: "2025-02-25 " tag: "3.1.3"
checkout support/3.2
commit id: "2025-02-25 " tag: "3.2.1"
commit id: "2025-04-08" tag: "3.2.1-1"
commit id: "2025-08-19" tag: "3.2.2-1"
checkout support/2.7
commit id: "2025-10-07" tag: "2.7.13"
```
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<title><![CDATA[iTop extensions]]></title>
<parser>
<target>../data/phpdocumentor/temp/extensions</target>
</parser>
<transformer>
<target>../data/phpdocumentor/output/extensions</target>
</transformer>
<transformations>
<template name="phpdoc-templates/combodo-wiki"/>
</transformations>
<files>
<file>../application/applicationextension.inc.php</file>
</files>
</phpdoc>

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<!--
/**
The documentation of this file can be found here : https://docs.phpdoc.org/references/configuration.html
it has to be completed by the CLI parameters documentation which is more comprehensive: https://docs.phpdoc.org/references/commands/project_run.html#usage
usage:
vendor/bin/phpdoc -c phpdoc-objects-manipulation.dist.xml
*/
-->
<title><![CDATA[iTop's objects manipulation API]]></title>
<parser>
<default-package-name>iTopORM</default-package-name>
<target>../data/phpdocumentor/temp/objects-manipulation</target>
<visibility>public,protected</visibility>
<markers>
<!--<item>TODO</item>-->
<!--<item>FIXME</item>-->
</markers>
<extensions>
<extension>php</extension>
</extensions>
</parser>
<transformer>
<target>../data/phpdocumentor/output/objects-manipulation</target>
</transformer>
<transformations>
<template name="phpdoc-templates/combodo-wiki"/>
</transformations>
<!--<logging>-->
<!--<level>warn</level>-->
<!--<paths>-->
<!--&lt;!&ndash;<default>data/phpdocumentor/log/objects-manipulation/{DATE}.log</default>&ndash;&gt;-->
<!--&lt;!&ndash;<errors>data/phpdocumentor/log/objects-manipulation/{DATE}.errors.log</errors>&ndash;&gt;-->
<!--<default>{APP_ROOT}/data/log/{DATE}.log</default>-->
<!--<errors>{APP_ROOT}/data/log/{DATE}.errors.log</errors>-->
<!--</paths>-->
<!--</logging>-->
<files>
<file>../core/dbobject.class.php</file>
<file>../core/dbobjectsearch.class.php</file>
<file>../core/metamodel.class.php</file>
<file>../core/dbobjectset.class.php</file>
<file>../core/dbsearch.class.php</file>
<file>../core/dbunionsearch.class.php</file>
</files>
</phpdoc>

View File

@@ -1,136 +0,0 @@
{% extends 'layout.txt.twig' %}
{% block content %}
<wrap button>[[start|🔙 Back]]</wrap>
{% if node.tags['internal'] is defined %}
====== {{ node.name }} ======
<WRAP alert>This class is "internal", and thus is not documented!</WRAP>
{% elseif node.tags['api'] is not defined and node.tags['api-advanced'] is not defined and node.tags['overwritable-hook'] is not defined and node.tags['extension-hook'] is not defined %}
====== {{ node.name }} ======
<WRAP alert>This class is neither "api", "api-advanced", "overwritable-hook" or "extension-hook", and thus is not documented!</WRAP>
{% else %}
====== {{ node.name }} ======
{% if node.deprecated %}<wrap danger>deprecated</wrap>{% endif %}
{% if node.abstract %}<wrap warning>abstract</wrap>{% endif %}
{% if node.final %}<wrap notice>final</wrap>{% endif %}
{% include 'includes/wrap-tags.txt.twig' with {structure:node, wrap: 'safety', wrapTags: ['api', 'api-advanced', 'overwritable-hook', 'extension-hook']} %}
{% if node.deprecated %}
=== **<del>Deprecated</del>**===
//{{ node.tags.deprecated[0].description }}//
{% endif %}
== {{ node.summary|replace({"\n":""})|raw }} ==
<html>{{ node.description|markdown|raw }}</html>
{% include 'includes/code-examples.txt.twig' with {structure:node, title_level: '====='} %}
{% set class = node.parent %}
{% block hierarchy_element %}
{% if class and class.name is defined and class.name|trim != '' %}
==== parent ====
{% set child = class %}
{% set class = class.parent %}
{{ block('hierarchy_element') }}
[[{{ child.name }}|{{ child.name }}]]
{% endif %}
{% endblock %}
{% for interface in node.interfaces|sort_asc %}
{% if loop.first %}
==== Implements ====
{% endif %}
{% if loop.length > 1 %} * {% endif %}{{ interface.fullyQualifiedStructuralElementName ?: interface }}
{% endfor %}
{% for trait in node.usedTraits|sort_asc %}
{% if loop.first %}
==== Uses traits ====
{% endif %}
{% if loop.length > 1 %} * {% endif %}{{ trait.fullyQualifiedStructuralElementName ?: trait }}
{% endfor %}
{% include 'includes/see-also.txt.twig' with {structure:node, title_level: '==='} %}
{% include 'includes/tags.txt.twig' with {structure:node, title_level: '=====', blacklist: ['link', 'see', 'abstract', 'example', 'method', 'property', 'property-read', 'property-write', 'package', 'subpackage', 'phpdoc-tuning-exclude-inherited', 'api', 'api-advanced', 'overwritable-hook', 'extension-hook', 'copyright', 'license', 'code-example']} %}
{% set methods = node.inheritedMethods.merge(node.methods.merge(node.magicMethods)) %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'api'} %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'api-advanced'} %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'overwritable-hook'} %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'extension-hook'} %}
{% include 'includes/code-examples.txt.twig' with {structure:node, title_level: '=====', sub_title_level: '=='} %}
<WRAP clear />
{% for method in methods|sort_asc
if method.visibility == 'public'
and (
method.tags['api'] is defined
or method.tags['api-advanced'] is defined
or method.tags['overwritable-hook'] is defined
or method.tags['extension-hook'] is defined
)
and (
node.tags['phpdoc-tuning-exclude-inherited'] is not defined
or method.parent.name == node.name
)
%}
{%- if loop.first %}
===== Public methods =====
{% endif %}
{{ block('method') }}
{% endfor %}
{% for method in methods|sort_asc if method.visibility == 'protected' and (method.tags['overwritable-hook'] is defined or method.tags['extension-hook'] is defined) %}
{%- if loop.first %}
===== Protected methods =====
{% endif %}
{{ block('method') }}
{% endfor %}
{% set constants = node.inheritedConstants.merge(node.constants) %}
{% if constants|length > 0 %}
===== Constants =====
{% for constant in constants|sort_asc %}
{{ block('constant') }}
{% endfor %}
{% endif %}
{#{% set properties = node.inheritedProperties.merge(node.properties.merge(node.magicProperties)) %}#}
{#{% for property in properties|sort_asc if property.visibility == 'public' %}#}
{#{%- if loop.first %}#}
{#===== Public properties =====#}
{#{% endif %}#}
{#{{ block('property') }}#}
{#{% endfor %}#}
{#{% for property in properties|sort_asc if property.visibility == 'protected' %}#}
{#{%- if loop.first %}#}
{#===== Protected properties =====#}
{#{% endif %}#}
{#{{ block('property') }}#}
{#{% endfor %}#}
{%- endif %} {#{% elseif node.tags['xxx'] is not defined and ... #}
<wrap button>[[start|🔙 Back]]</wrap>
{% endblock %}

View File

@@ -1,31 +0,0 @@
{% block constant %}
<WRAP group box >
<WRAP twothirds column >
==== {{ constant.name }} ====
</WRAP>{# twothirds column#}
<WRAP third column>
{% if constant.deprecated %}<wrap danger>deprecated</wrap> {% endif %}
{% if (node.parent is not null and constant.parent.fullyQualifiedStructuralElementName != node.fullyQualifiedStructuralElementName) %}<wrap notice>inherited</wrap> {% endif %}
</WRAP>{# third column#}
== {{ constant.summary|replace({"\n":""})|raw }} ==
<html>{{ constant.description|markdown|raw }}</html>
{% if constant.deprecated %}
=== Deprecated ===
{{ constant.tags.deprecated[0].description|raw }}
{% endif %}
{% include 'includes/inherited-from.txt.twig' with {structure:constant} %}
{% include 'includes/see-also.txt.twig' with {structure:constant, title_level: '=='} %}
{% include 'includes/uses.txt.twig' with {structure:constant, title_level: '=='} %}
{% include 'includes/tags.txt.twig' with {structure:constant, title_level: '==', blacklist: ['link', 'see', 'var', 'deprecated', 'uses', 'package', 'subpackage', 'todo', 'code-example']} %}
</WRAP>{# group #}
{% endblock %}

View File

@@ -1,95 +0,0 @@
{% block method %}
<WRAP group box >
<WRAP twothirds column >
==== {{ method.name }} ====
</WRAP>{# twothirds column#}
<WRAP third column >
{% include 'includes/wrap-tags.txt.twig' with {structure:method, wrap: 'safety', wrapTags: ['api', 'api-advanced', 'overwritable-hook', 'extension-hook']} %}
{% if method.deprecated %}<wrap danger>deprecated</wrap> {% endif %}
{% if (node.parent is not null and method.parent.fullyQualifiedStructuralElementName != node.fullyQualifiedStructuralElementName) %}<wrap notice>inherited</wrap> {% endif %}
{% if method.abstract %}<wrap warning>abstract</wrap> {% endif %}
{% if method.final %}<wrap notice>final</wrap> {% endif %}
<wrap notice>{{ method.visibility }}</wrap>
{% if method.static %}<wrap warning>static</wrap> {% endif %}
</WRAP>{# third column#}
== {{ method.summary|replace({"\n":""})|raw }} ==
<html>{{ method.description|markdown|raw }}</html>
<code php>{% if method.abstract %}abstract {% endif %}{% if method.final %}final {% endif %}{{ method.visibility }} {% if method.static %}static {% endif %}{{ method.name }}({% for argument in method.arguments %}{{ argument.isVariadic ? '...' }}{{ argument.name }}{{ argument.default ? (' = '~argument.default)|raw }}{% if not loop.last %}, {% endif %}{% endfor %})</code>
<WRAP twothirds column >
=== Parameters ===
{% if method.arguments|length > 0 -%}
^ types ^ name ^ default ^ description ^
{% for argument in method.arguments -%}
| **<nowiki>{{ argument.types|join('|')|raw }}</nowiki>** | {{ argument.name }} {{ argument.isVariadic ? '<small style="color: gray">variadic</small>' }} | <nowiki>{{ argument.default|raw }}</nowiki> | {{ argument.description|trim|replace("\n", ' ')|raw }} |{{ "\r\n" }}
{%- endfor %}
{% else %}
//none//
{% endif %}
{#=== Parameters ===#}
{#{% if method.arguments|length > 0 -%}#}
{#{% for argument in method.arguments -%}#}
{#== {{ argument.name }} ==#}
{#{% set varDesc %}#}
{#<span style="margin:0 10px; 0 20px; font-weight: bold;">{{ argument.types|join('|') }}</span>#}
{#{{ argument.isVariadic ? '<small style="color: gray">variadic</small>' }}#}
{#{{ argument.description|raw }}#}
{#{% endset %}#}
{#<html>{{ varDesc|markdown|raw }}</html>#}
{#{%- endfor %}#}
{#{% else %}#}
{#<wrap tip>This method has no parameter</wrap>#}
{#{% endif %}#}
{% if method.response and method.response.types|join() != 'void' %}
=== Returns ===
<html>{{ ('**' ~ method.response.types|join('|')|trim ~ '** ' ~ method.response.description)|markdown|raw }}</html>
{% endif %}
</WRAP>{# twothirds column#}
<WRAP third column >
{% if method.tags.throws|length > 0 or method.tags.throw|length > 0 %}
=== Throws ===
{% for exception in method.tags.throws -%}
{% if loop.length > 1 %} * {% endif %}''{{ exception.types|join('|')|raw }}'' <nowiki>{{ exception.description|raw }}</nowiki>
{% endfor %}
{% endif %}
{% include 'includes/inherited-from.txt.twig' with {structure:method} %}
{% include 'includes/see-also.txt.twig' with {structure:method, title_level: '==='} %}
{% include 'includes/uses.txt.twig' with {structure:method, title_level: '==='} %}
{% include 'includes/used-by.txt.twig' with {structure:method, title_level: '==='} %}
{% include 'includes/tags-with-description.txt.twig' with {structure:method, title_level: '===', WRAP: 'info', tagsWithDescription: ['api', 'api-advanced', 'overwritable-hook', 'extension-hook']} %}
{% include 'includes/tags.txt.twig' with {structure:method, title_level: '===', blacklist: ['todo', 'link', 'see', 'abstract', 'example', 'param', 'return', 'access', 'deprecated', 'throws', 'throw', 'uses', 'api', 'api-advanced', 'overwritable-hook', 'extension-hook', 'used-by', 'inheritdoc', 'code-example']} %}
</WRAP>{# third column#}
{% include 'includes/code-examples.txt.twig' with {structure:method, title_level: '==='} %}
</WRAP>{# group #}
{% endblock %}

View File

@@ -1,49 +0,0 @@
{% block property %}
<WRAP group box>
<WRAP twothirds column >
==== ${{ property.name }} ====
</WRAP>{# twothirds column#}
<WRAP third column>
{% if property.deprecated %}<wrap danger>deprecated</wrap> {% endif %}
{% if (node.parent is not null and property.parent.fullyQualifiedStructuralElementName != node.fullyQualifiedStructuralElementName) %}<wrap notice>inherited</wrap> {% endif %}
</WRAP>{# third column#}
== {{ property.summary|replace({"\n":""})|raw }} ==
<html>{{ property.description|markdown|raw }}</html>
{% if property.var.0.description %}<html>{{ property.var.0.description|markdown|raw }}</html>{% endif %}
{#{% if property.types %}#}
{#== Type ==#}
{#{% for type in property.types %}#}
{#{% if loop.length > 1 %} * {% endif %}{{ type|raw }} : {{ type.description|raw }}#}
{#{% endfor %}#}
{#{{ property.types|join('|')|raw }}#}
{#{% endif %}#}
{% if property.deprecated %}
== Deprecated ==
{{ property.tags.deprecated[0].description }}
{% endif %}
{% include 'includes/inherited-from.txt.twig' with {structure:property} %}
{% include 'includes/see-also.txt.twig' with {structure:property, title_level: '=='} %}
{% include 'includes/uses.txt.twig' with {structure:property, title_level: ''} %}
{% include 'includes/tags.txt.twig' with {structure:property, title_level: '==', blacklist: ['link', 'see', 'access', 'var', 'deprecated', 'uses', 'todo', 'code-example']} %}
<code php>{{ property.visibility }} ${{ property.name }}{% if property.types %} : {{ property.types|join('|')|raw }}{% endif %}</code>
</WRAP>{# group #}
{% endblock %}

View File

@@ -1 +0,0 @@
{{ node.source|raw }}

View File

@@ -1,122 +0,0 @@
{% extends 'layout.txt.twig' %}
{% block javascripts %}
{% endblock %}
{% block content %}
{#<section class="row-fluid">#}
{#<div class="span2 sidebar">#}
{#{% set namespace = project.namespace %}#}
{#{{ block('sidebarNamespaces') }}#}
{#</div>#}
{#</section>#}
{#<section class="row-fluid">#}
====== {{ node.path|split('/')|slice(0,-1)|join('/') }}{{ node.name }} ======
{{ node.summary }}
<html>{{ node.description|markdown|raw }}</html>
{% if node.traits|length > 0 %}
===== Traits =====
{% for trait in node.traits %}
<tr>
<td>{{ trait|raw }}</td>
<td><em>{{ trait.summary }}</em></td>
</tr>
{% endfor %}
{% endif %}
{% if node.interfaces|length > 0 %}
===== Interfaces =====
{% for interface in node.interfaces %}
<tr>
<td>{{ interface|raw }}</td>
<td><em>{{ interface.summary }}</em></td>
</tr>
{% endfor %}
{% endif %}
{% if node.classes|length > 0 %}
===== Classes =====
{% for class in node.classes %}
{{ class|raw }}
<em>{{ class.summary }}</em>
{% endfor %}
{% endif %}
{% if node.package is not empty and node.package != '\\' %}
===== Package =====
{{ node.subpackage ? (node.package ~ '\\' ~ node.subpackage) : node.package }}
{% endif %}
{% for tagName,tags in node.tags if tagName in ['link', 'see'] %}
{% if loop.first %}
===== See also =====
{% endif %}
{% for tag in tags %}
<dd><a href="{{ tag.reference ?: tag.link }}"><div class="namespace-wrapper">{{ tag.description ?: tag.reference }}</div></a></dd>
{% endfor %}
{% endfor %}
<h2>Tags</h2>
<table class="table table-condensed">
{% for tagName,tags in node.tags if tagName not in ['link', 'see', 'package', 'subpackage'] %}
<tr>
<th>
{{ tagName }}
</th>
<td>
{% for tag in tags %}
{{ tag.description|markdown|raw }}
{% endfor %}
</td>
</tr>
{% else %}
<tr><td colspan="2"><em>None found</em></td></tr>
{% endfor %}
</table>
</aside>
</div>
{% if node.constants|length > 0 %}
<div class="row-fluid">
<section class="span8 content file">
<h2>Constants</h2>
</section>
<aside class="span4 detailsbar"></aside>
</div>
{% for constant in node.constants %}
{{ block('constant') }}
{% endfor %}
{% endif %}
{% if node.functions|length > 0 %}
<div class="row-fluid">
<section class="span8 content file">
<h2>Functions</h2>
</section>
<aside class="span4 detailsbar"></aside>
</div>
{% for method in node.functions %}
{{ block('method') }}
{% endfor %}
{% endif %}
</div>
</section>
<div id="source-view" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="source-view-label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="source-view-label">{{ node.file.name }}</h3>
</div>
<div class="modal-body">
<pre data-src="{{ path('files/' ~ node.path ~ '.txt')|raw }}" class="language-php line-numbers"></pre>
</div>
</div>
{% endblock %}

View File

@@ -1,42 +0,0 @@
{% extends 'layout.html.twig' %}
{% block stylesheets %}
<link href="{{ path('css/jquery.iviewer.css') }}" rel="stylesheet" media="all"/>
<style>
#viewer {
position: relative;
width: 100%;
}
.wrapper {
overflow: hidden;
}
</style>
{% endblock %}
{% block javascripts %}
<script src="{{ path('js/jquery.mousewheel.js') }}" type="text/javascript"></script>
<script src="{{ path('js/jquery.iviewer.js') }}" type="text/javascript"></script>
<script type="text/javascript">
$(window).resize(function(){
$("#viewer").height($(window).height() - 100);
});
$(document).ready(function() {
$("#viewer").iviewer({src: '{{ path('graphs/classes.svg') }}', zoom_animation: false});
$('#viewer img').on('dragstart', function(event){
event.preventDefault();
});
$(window).resize();
});
</script>
{% endblock %}
{% block content %}
<div class="row-fluid">
<div class="span12">
<div class="wrapper">
<div id="viewer" class="viewer"></div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,5 +0,0 @@
# Fixes a vulnerability in CentOS: http://stackoverflow.com/questions/20533279/prevent-php-from-parsing-non-php-files-such-as-somefile-php-txt
<FilesMatch \.php\.txt$>
RemoveHandler .php
ForceType text/plain
</FilesMatch>

View File

@@ -1,34 +0,0 @@
{% if title_level is not defined %}
{%- set title_level = '==' -%}
{% endif %}
{% if sub_title_level is not defined %}
{%- set sub_title_level = title_level|slice(1) -%}
{% endif %}
{% if sub_title_level == '=' %}
{%- set sub_title_level = '' -%}
{% endif %}
{#{% for tagName,tags in structure.tags if tagName in ['code-example'] %}#}
{#{% if loop.first %}#}
{#{{title_level}} Examples {{title_level}}#}
{#{% endif %}#}
{#{% for tag in tags %}#}
{#{%- set descToken = tag.description|split("\n", 2) -%}#}
{#{%- set title = descToken[0] -%}#}
{#{%- set code = descToken[1] -%}#}
{#{{sub_title_level}} {{ title }} {{sub_title_level}}#}
{#<code php>{{ code|raw }}</code>#}
{#{% endfor %}#}
{#{% endfor %}#}
{% for tagName,tags in structure.tags if tagName in ['example'] %}
{% if loop.first %}
{{title_level}} Examples {{title_level}}
{% endif %}
{% for tag in tags %}
{{ sub_title_level }} {{ tag.filePath|escape }}{{ sub_title_level }}
<code php>{{ tag.description|raw }}</code>
{% endfor %}
{% endfor %}

View File

@@ -1,12 +0,0 @@
{% if title_level is not defined %}
{% set title_level='' %}
{% endif %}
{% if (node.parent is null) %}
{{title_level}} File {{ structure.path }} {{title_level}}
{% endif %}
{% if (node.parent is not null and structure.parent.fullyQualifiedStructuralElementName != node.fullyQualifiedStructuralElementName) %}
{{title_level}} Inherited from {{title_level}}
[[{{structure.parent}}|{{structure.parent}}]]
{% endif %}

View File

@@ -1,26 +0,0 @@
{% for structure in structures|sort_asc if structure.tags['internal'] is not defined and (structure.tags['api'] is defined or structure.tags['api-advanced'] is defined or structure.tags['overwritable-hook'] is defined or structure.tags['extension-hook'] is defined ) %}
{#{{ structure|raw }}#}
{% set structureName = structure|trim('\\', 'left') %}
<WRAP group box>
<WRAP twothirds column >
==== {{ structureName }} ====
</WRAP>{# twothirds column#}
<WRAP third column>
{% if structure.deprecated %}<wrap danger>deprecated</wrap>{% endif %}
{% if structure.abstract %}<wrap warning>abstract</wrap>{% endif %}
{% if structure.final %}<wrap notice>final</wrap>{% endif %}
{% if (node.parent is not null and structure.parent.fullyQualifiedStructuralElementName != node.fullyQualifiedStructuralElementName) %}<wrap notice>inherited</wrap> {% endif %}
{% include 'includes/wrap-tags.txt.twig' with {structure:structure, wrap: 'safety', wrapTags: ['api', 'api-advanced', 'overwritable-hook', 'extension-hook']} %}
</WRAP>{# third column#}
{{ structure.summary|raw }}
[[{{structureName}}|More information]]
</WRAP>{# group #}
{% endfor %}

View File

@@ -1,26 +0,0 @@
{% if title_level is not defined %}
{%- set title_level='==' -%}
{% endif %}
{% for tagName,tags in structure.tags if tagName in ['link', 'see'] %}
{% if loop.first %}
{{title_level}} See also {{title_level}}
{% endif %}
{% for tag in tags %}
{%- set linkTag = tag.reference|trim('\\', 'left') -%}
{% if not('()' in linkTag or '$' in linkTag or node.name in linkTag or '::' in linkTag ) %}
{%- set linkTag = linkTag|lower -%}
{% elseif node.name~'::' in linkTag %}
{%- set linkTag = linkTag|replace({(node.name~'::'): '#'})|lower -%}
{% elseif '::' in linkTag -%}
{%- set linkTag = linkTag|replace({'::': '#'})|lower -%}
{% else %}
{%- set linkTag = '#' ~ linkTag|lower -%}
{%- endif %}
{% if loop.length > 1 %} * {% endif %}{% if tag.reference is not empty -%}
[[{{linkTag}}|{{ (tag.reference)|trim('\\', 'left') }}]] {% if tag.description|trim is not empty %}: {{ tag.description|trim('\\', 'left') }} {% endif %}
{%- else -%}
{#{{ tag.description|trim('\\', 'left') }}#}
{% endif %}
{% endfor %}
{% endfor %}

View File

@@ -1,56 +0,0 @@
{% if tag is not defined -%}
{# Do not display @api if @api-advanced is also present #}
{%- set tag = "api" -%}
{%- endif %}
{% if hidden_by is not defined -%}
{# Do not display @api if @api-advanced is also present #}
{%- set hidden_by = {"api" : "api-advanced"} -%}
{%- endif %}
{% for method in methods|sort_asc
if (method.visibility == 'public')
and (
method.tags[tag] is defined
and (
hidden_by[tag] is not defined or method.tags[hidden_by[tag]] is not defined
)
)
%}
{%- if loop.first %}
{% if tag == 'api' %}
===== API synthesis =====
<WRAP>
List of the public API methods.
When manipulating {{ node.name }}, You can call those methods:
</WRAP>
{% elseif tag == 'api-advanced' %}
===== Advanced API synthesis =====
<WRAP>
List of advanced API methods
Beware they usage is recommended to advanced users only.
</WRAP>
{% elseif tag == 'overwritable-hook' %}
===== overwritable-hook synthesis =====
<WRAP >When inheriting from {{ node.name }},
you can overwrite those methods in order to add custom logic:
</WRAP>
{% elseif tag == 'extension-hook' %}
===== extension-hook synthesis =====
<WRAP >
When inheriting from {{ node.name }},
you can extend the behaviour of iTop by implementing:
</WRAP>
{% endif %}
{% endif %}
{% set sanitizedMethod = method|trim('\\', 'left')|replace({(node.name~'::'): ''}) %}
{% if '::' in sanitizedMethod -%}
{%- if node.tags['phpdoc-tuning-exclude-inherited'] is not defined %}
* [[{{sanitizedMethod|replace({'::': '#'})|lower}}|↪{{sanitizedMethod}}]] — {{ method.summary|replace({"\n":""})|raw }}
{% endif %}
{%- else %}
* [[#{{sanitizedMethod}}|{{sanitizedMethod}}]] — {{ method.summary|replace({"\n":""})|raw }}
{% endif %}
{% endfor %}

View File

@@ -1,20 +0,0 @@
{% if title_level is not defined %}
{% set title_level = '==' %}
{% endif %}
{%- for tagName,tags in structure.tags if tagName in tagsWithDescription -%}
{%- for tag in tags -%}
{%- if tag.description is not empty -%}
{%- if WRAP is defined -%}
<WRAP {{WRAP}}>
{%- endif -%}
{{title_level}} {{ tagName }} {{title_level}}
{{ tag.description|escape }}
{%- if WRAP is defined -%}
</WRAP>
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- endfor -%}

View File

@@ -1,22 +0,0 @@
{% if title_level is not defined %}
{% set title_level='=====' %}
{% endif %}
{% if blacklist is not defined %}
{% set blacklist =['link', 'see', 'abstract', 'example', 'method', 'property', 'property-read', 'property-write', 'package', 'subpackage', 'api', 'api-advanced', 'todo', 'code-example'] %}
{% endif %}
{% if hidden_by is not defined -%}
{# Do not display @api if @api-advanced is also present #}
{%- set hidden_by = {"api" : "api-advanced"} -%}
{%- endif %}
{#^ {% for tagName,tags in structure.tags if tagName not in blacklist -%}#}
{#{{ tagName }} ^#}
{#{%- endfor %}#}
{% for tagName,tags in structure.tags if tagName not in blacklist and (hidden_by[tagName] is not defined or structure.tags[hidden_by[tagName]] is not defined) %}
{%- if loop.first %}
{{title_level}} Tags {{title_level}}
{% endif %}
^ {{ tagName }} | {% for tag in tags %}{{ tag.version ? tag.version ~ ' ' : '' }}{{ tag.description}}{% endfor %} |
{% endfor %}

View File

@@ -1,24 +0,0 @@
{% if title_level is not defined %}
{% set title_level='' %}
{% endif %}
{% for tagName,tags in structure.tags if tagName in ['used-by'] %}
{% if loop.first %}
{{title_level}} Used by {{title_level}}
{% endif %}
{% for tag in tags %}
{% if loop.length > 1 %} * {% endif %}{{ tag.reference ?: tag.link }} : {{ tag.description ?: tag.reference }}
{% endfor %}
{% endfor %}
{#{% for tagName,tags in method.tags if tagName in ['uses'] %}#}
{#{% if loop.first %}#}
{#<dt>Uses</dt>#}
{#{% endif %}#}
{#{% for tag in tags %}#}
{#<dd>{{ tag.reference|raw }}</dd>#}
{#{% endfor %}#}
{#{% endfor %}#}

View File

@@ -1,24 +0,0 @@
{% if title_level is not defined %}
{% set title_level='' %}
{% endif %}
{% for tagName,tags in structure.tags if tagName in ['uses'] %}
{% if loop.first %}
{{title_level}} Uses {{title_level}}
{% endif %}
{% for tag in tags %}
{% if loop.length > 1 %} * {% endif %}{{ tag.reference ?: tag.link }} : {{ tag.description ?: tag.reference }}
{% endfor %}
{% endfor %}
{#{% for tagName,tags in method.tags if tagName in ['uses'] %}#}
{#{% if loop.first %}#}
{#<dt>Uses</dt>#}
{#{% endif %}#}
{#{% for tag in tags %}#}
{#<dd>{{ tag.reference|raw }}</dd>#}
{#{% endfor %}#}
{#{% endfor %}#}

View File

@@ -1,11 +0,0 @@
{% if wrap is not defined -%}
{% set wrap = 'notice' %}
{%- endif -%}
{% if hidden_by is not defined -%}
{# Do not display @api if @api-advanced is also present #}
{%- set hidden_by = {"api" : "api-advanced"} -%}
{%- endif %}
{%- for tagName,tags in structure.tags if tagName in wrapTags and (hidden_by[tagName] is not defined or structure.tags[hidden_by[tagName]] is not defined) %}
<wrap {{wrap}}>{{tagName}}</wrap>
{% endfor %}

View File

@@ -1,121 +0,0 @@
{% extends 'layout.txt.twig' %}
{% block content %}
<wrap button>[[start|🔙 Back]]</wrap>
{% if node.tags['internal'] is defined %}
====== {{ node.name }} ======
<WRAP alert>This interface is "internal", and thus is not documented!</WRAP>
{% elseif node.tags['api'] is not defined and node.tags['api-advanced'] is not defined and node.tags['overwritable-hook'] is not defined and node.tags['extension-hook'] is not defined %}
====== {{ node.name }} ======
<WRAP alert>This interface is neither "api", "overwritable-hook" or "extension-hook", and thus is not documented!</WRAP>
{% else %}
====== {{ node.name }} ======
{% if node.deprecated %}<wrap danger>deprecated</wrap>{% endif %}
{% if node.abstract %}<wrap warning>abstract</wrap>{% endif %}
{% if node.final %}<wrap notice>final</wrap>{% endif %}
{% include 'includes/wrap-tags.txt.twig' with {structure:node, wrap: 'safety', wrapTags: ['api', 'api-advanced', 'overwritable-hook', 'extension-hook']} %}
{% if node.deprecated %}
=== **<del>Deprecated</del>**===
//{{ node.tags.deprecated[0].description }}//
{% endif %}
== {{ node.summary|replace({"\n":""})|raw }} ==
<html>{{ node.description|markdown|raw }}</html>
{% include 'includes/code-examples.txt.twig' with {structure:node, title_level: '====='} %}
{% set class = node.parent %}
{% block hierarchy_element %}
{% if class and class.name is defined and class.name|trim != '' %}
==== parent ====
{% set child = class %}
{% set class = class.parent %}
{{ block('hierarchy_element') }}
[[{{ child.name }}|{{ child.name }}]]
{% endif %}
{% endblock %}
{% for interface in node.interfaces|sort_asc %}
{% if loop.first %}
==== Implements ====
{% endif %}
{% if loop.length > 1 %} * {% endif %}{{ interface.fullyQualifiedStructuralElementName ?: interface }}
{% endfor %}
{% for trait in node.usedTraits|sort_asc %}
{% if loop.first %}
==== Uses traits ====
{% endif %}
{% if loop.length > 1 %} * {% endif %}{{ trait.fullyQualifiedStructuralElementName ?: trait }}
{% endfor %}
{% include 'includes/see-also.txt.twig' with {structure:node, title_level: '==='} %}
{% include 'includes/tags.txt.twig' with {structure:node, title_level: '=====', blacklist: ['link', 'see', 'abstract', 'example', 'method', 'property', 'property-read', 'property-write', 'package', 'subpackage', 'phpdoc-tuning-exclude-inherited', 'api', 'api-advanced', 'overwritable-hook', 'extension-hook', 'copyright', 'license', 'code-example']} %}
{% set methods = node.inheritedMethods.merge(node.methods) %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'api'} %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'api-advanced'} %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'overwritable-hook'} %}
{% include 'includes/tag-synthesys.txt.twig' with {methods:methods, tag:'extension-hook'} %}
<WRAP clear />
{% for method in methods|sort_asc if method.visibility == 'public' %}
{%- if loop.first %}
===== Public methods =====
{% endif %}
{{ block('method') }}
{% endfor %}
{% for method in methods|sort_asc if method.visibility == 'protected' %}
{%- if loop.first %}
===== Protected methods =====
{% endif %}
{{ block('method') }}
{% endfor %}
{% set constants = node.inheritedConstants.merge(node.constants) %}
{% if constants|length > 0 %}
===== Constants =====
{% for constant in constants|sort_asc %}
{{ block('constant') }}
{% endfor %}
{% endif %}
{#{% set properties = node.inheritedProperties.merge(node.properties) %}#}
{#{% for property in properties|sort_asc if property.visibility == 'public' %}#}
{#{%- if loop.first %}#}
{#===== Public properties =====#}
{#{% endif %}#}
{#{{ block('property') }}#}
{#{% endfor %}#}
{#{% for property in properties|sort_asc if property.visibility == 'protected' %}#}
{#{%- if loop.first %}#}
{#===== Protected properties =====#}
{#{% endif %}#}
{#{{ block('property') }}#}
{#{% endfor %}#}
{%- endif %} {#{% elseif node.tags['xxx'] is not defined and ... #}
<wrap button>[[start|🔙 Back]]</wrap>
{% endblock %}

View File

@@ -1,5 +0,0 @@
{% use 'elements/constant.txt.twig' %}
{% use 'elements/property.txt.twig' %}
{% use 'elements/method.txt.twig' %}
{% block content %}{% endblock %}

View File

@@ -1,51 +0,0 @@
{% extends 'layout.txt.twig' %}
{% block content %}
{% set namespace = project.namespace %}
{{ block('sidebarNamespaces') }}
{#{{ node.parent|raw }}#}
{#====== {{ node.parent.fullyQualifiedStructuralElementName }}{{ node.name }} ======#}
{% if node.children|length > 0 %}
=====Namespaces=====
{% include 'includes/namespace-structure-toc.html.twig' with {structures: node.children} %}
----
{% endif %}
{% if node.traits|length > 0 %}
===== Traits =====
{% include 'includes/namespace-structure-toc.html.twig' with {structures: node.traits} %}
----
{%- endif %}
{% if node.interfaces|length > 0 %}
===== Interfaces =====
{% include 'includes/namespace-structure-toc.html.twig' with {structures: node.interfaces} %}
----
{% endif %}
{% if node.classes|length > 0 %}
===== Classes =====
{% include 'includes/namespace-structure-toc.html.twig' with {structures: node.classes} %}
----
{% endif %}
{#{% if node.constants|length > 0 %}#}
{#===== Constants =====#}
{#{% for constant in node.constants|sort_asc %}#}
{# {{ block('constant') }}#}
{#{% endfor %}#}
{#{% endif %}#}
{#{% if node.functions|length > 0 %}#}
{#===== Functions =====#}
{#{% for method in node.functions|sort_asc %}#}
{# {{ block('method') }}#}
{#{% endfor %}#}
{#{% endif %}#}
{% endblock %}

View File

@@ -1,49 +0,0 @@
====== Deprecated elements ======
{#{% for element in project.indexes.elements if element.deprecated %}#}
{#{% if element.file.path != previousPath %}#}
{#<li><a href="#{{ element.file.path }}"><i class="icon-file"></i> {{ element.file.path }}</a></li>#}
{#{% endif %}#}
{#{% set previousPath = element.file.path %}#}
{#{% endfor %}#}
{% for element in project.indexes.elements if element.deprecated %}
{% if element.file.path != previousPath %}
{% if previousPath %}
</WRAP>{# group #}
{% endif %}
{#<a name="{{ element.file.path }}" id="{{ element.file.path }}"></a>#}
===== {{ element.file.path }} ({{ element.tags.deprecated.count }} found)=====
<WRAP group >
<WRAP third column >
Element
</WRAP>{# third column#}
<WRAP third column >
Line
</WRAP>{# third column#}
<WRAP third column >
Description
</WRAP>{# third column#}
{% endif %}
{% for tag in element.tags.deprecated %}
<WRAP group >
<WRAP third column >
{{ element.fullyQualifiedStructuralElementName }}
</WRAP>{# third column#}
<WRAP third column >
{{ element.line }}
</WRAP>{# third column#}
<WRAP third column >
{{ tag.description }}
</WRAP>{# third column#}
{% endfor %}
</WRAP>{# group #}
{% set previousPath = element.file.path %}
{% else %}
<WRAP info>No deprecated elements have been found in this project.</WRAP>
{% endfor %}

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<template>
<author>Bruno DA SILVA</author>
<email>contact [at] combodo.com</email>
<version>1.0.0</version>
<copyright>Combodo 2018</copyright>
<description><![CDATA[
Forked from the clean theme of https://github.com/phpDocumentor/phpDocumentor2 provided under the MIT licence.
The original work is copyright "Mike van Riel".
------------------------------------------------------------------------------------------------------------------
To improve performance you can add the following to your .htaccess:
<ifModule mod_deflate.c>
<filesMatch "\.(js|css|html)$">
SetOutputFilter DEFLATE
</filesMatch>
</ifModule>
]]></description>
<transformations>
<transformation writer="twig" query="namespace" source="templates/combodo-wiki/namespace.txt.twig" artifact="start.txt"/>
<transformation writer="twig" query="indexes.classes" source="templates/combodo-wiki/class.txt.twig" artifact="{{name}}.txt"/>
<transformation writer="twig" query="indexes.interfaces" source="templates/combodo-wiki/interface.txt.twig" artifact="{{name}}.txt" />
</transformations>
</template>

View File

@@ -7,6 +7,17 @@ Here are some guidelines that will help us integrate your work!
## Contributions
```
_ _ _ _ _ __ _
| | | | __ _ ___| | _| |_ ___ | |__ ___ _ __ / _| ___ ___| |_
| |_| |/ _` |/ __| |/ / __/ _ \| '_ \ / _ \ '__| |_ / _ \/ __| __|
| _ | (_| | (__| <| || (_) | |_) | __/ | | _| __/\__ \ |_
|_| |_|\__,_|\___|_|\_\\__\___/|_.__/ \___|_| |_| \___||___/\__|
```
This repository is part of Hacktoberfest. Contributions are welcome! Feel free to suggest improvements, add translation, fix bugs, or propose new features. Thank you for contributing !
### Subjects
You are welcome to create pull requests on any of those subjects:
@@ -161,4 +172,4 @@ We have one sticker per contribution type. You might get multiple stickers with
Here is the design of each stickers for year 2024:
![iTop stickers 2024](.doc/contributing-guide/2024.contributing-stickers-side-by-side.png)
![iTop stickers 2025](.doc/contributing-guide/2025.contributing-stickers-side-by-side.png)

View File

@@ -53,7 +53,7 @@ iTop also offers mass import tools to help you become even more efficient.
[4]: https://www.itophub.io/wiki/page?id=latest:install:requirements
[5]: https://www.itophub.io/wiki
[6]: https://store.itophub.io/en_US/
[7]: .doc/itop-version-history.md
[7]: itop-version-history.md
[10]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#configuration_management_cmdb
[11]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#ticketing
@@ -99,6 +99,7 @@ We would like to give a special thank you 🤗 to the people from the community
- Goethals, Stefan
- Giuva, Vincenzo Katriel (a.k.a [@DarkNight97boss](https://github.com/DarkNight97boss))
- Gumble, David
- Håkon, Harnes (a.k.a [@hakonharnes](https://github.com/hakonharnes))
- Heloir, Arthur
- Janssens, Jelle (a.k.a [@janssensjelle](https://github.com/janssensjelle))
- Ji, Leeb (冀利斌) (a.k.a [@chileeb](https://github.com/chileeb))

29
app.php
View File

@@ -1,29 +0,0 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
* This file is part of iTop.
*
* 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.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Kernel;
require_once __DIR__.'/lib/autoload_runtime.php';
require_once('approot.inc.php');
require_once('application/startup.inc.php');
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

View File

@@ -57,12 +57,6 @@ require_once(APPROOT.'application/applicationextension/backoffice/SeparatorPopup
require_once(APPROOT.'application/applicationextension/backoffice/URLButtonItem.php');
require_once(APPROOT.'application/applicationextension/backoffice/URLPopupMenuItem.php');
//deprecated class and interface
require_once(APPROOT.'application/applicationextension/backoffice/iApplicationObjectExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/AbstractApplicationObjectExtension.php');
require_once(APPROOT.'application/applicationextension/iBackupExtraFilesExtension.php');
require_once(APPROOT.'application/applicationextension/iKPILoggerExtension.php');
require_once(APPROOT.'application/applicationextension/iModuleExtension.php');

View File

@@ -1,58 +0,0 @@
<?php
/**
* Extend this class instead of iApplicationObjectExtension if you don't need to overload all methods
*
* @api
* @deprecated 3.1.0 N°4756 use the new event service instead, see {@see DBObject::FireEvent()} method
* @package ORMExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractApplicationObjectExtension implements iApplicationObjectExtension
{
/**
* @inheritDoc
*/
public function OnIsModified($oObject)
{
return false;
}
/**
* @inheritDoc
*/
public function OnCheckToWrite($oObject)
{
return array();
}
/**
* @inheritDoc
*/
public function OnCheckToDelete($oObject)
{
return array();
}
/**
* @inheritDoc
*/
public function OnDBUpdate($oObject, $oChange = null)
{
}
/**
* @inheritDoc
*/
public function OnDBInsert($oObject, $oChange = null)
{
}
/**
* @inheritDoc
*/
public function OnDBDelete($oObject, $oChange = null)
{
}
}

View File

@@ -1,111 +0,0 @@
<?php
/**
* Implement this interface to perform specific operations when objects are manipulated
*
* Note that those methods will be called when objects are manipulated, either in a programmatic way
* or through the GUI.
*
* @api
* @deprecated 3.1.0 N°4756 use the new event service instead, see {@see DBObject::FireEvent()} method. More details on each method PHPDoc.
* @package ORMExtensibilityAPI
*/
interface iApplicationObjectExtension
{
/**
* Invoked to determine whether an object has been modified in memory
*
* The GUI calls this verb to determine the message that will be displayed to the end-user.
* Anyhow, this API can be called in other contexts such as the CSV import tool.
*
* If the extension returns false, then the framework will perform the usual evaluation.
* Otherwise, the answer is definitively "yes, the object has changed".
*
* @param /cmdbAbstractObject $oObject The target object
*
* @return boolean True if something has changed for the target object
* @api
* @deprecated 3.1.0 N°4756 No alternative available, this API was unstable and is abandoned
*/
public function OnIsModified($oObject);
/**
* Invoked to determine whether an object can be written to the database
*
* The GUI calls this verb and reports any issue.
* Anyhow, this API can be called in other contexts such as the CSV import tool.
*
* @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.
* @api
* @deprecated 3.1.0 N°4756 Use EVENT_DB_CHECK_TO_WRITE event instead
*/
public function OnCheckToWrite($oObject);
/**
* Invoked to determine wether an object can be deleted from the database
*
* The GUI calls this verb and stops the deletion process if any issue is reported.
*
* Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled.
*
* @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.
* @api
* @deprecated 3.1.0 N°4756 Use EVENT_DB_CHECK_TO_DELETE event instead
*/
public function OnCheckToDelete($oObject);
/**
* Invoked when an object is updated into the database. The method is called right <b>after</b> the object has been written to the
* database.
*
* Useful methods you can call on $oObject :
*
* * {@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
*
* @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
*
* @return void
*
* @deprecated 3.1.0 N°4756 Use EVENT_DB_AFTER_WRITE event instead
* @api
* @since 2.7.0 N°2293 can access object changes by calling {@see DBObject::ListPreviousValuesForUpdatedAttributes()} on $oObject
*/
public function OnDBUpdate($oObject, $oChange = null);
/**
* Invoked when an object is created into the database
*
* The method is called right <b>after</b> the object has been written to the database.
*
* @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
*
* @return void
* @deprecated 3.1.0 N°4756 Use EVENT_DB_AFTER_WRITE event instead
* @api
*/
public function OnDBInsert($oObject, $oChange = null);
/**
* Invoked when an object is deleted from the database
*
* The method is called right <b>before</b> the object will be deleted from the database.
*
* @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
*
* @return void
* @deprecated 3.1.0 N°4756 Use EVENT_DB_AFTER_DELETE event instead
* @api
*/
public function OnDBDelete($oObject, $oChange = null);
}

View File

@@ -214,6 +214,15 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
*/
protected static bool $bBlockEventDBLinksChanged = false;
/**
* If set to true, the object is considered as modified, whatever the actual state is.
* This is used when an object is modified indirectly (eg. through a linked set)
*
* @var bool
*
* @since 3.3.0 N°8210 - Remove iApplicationObjectExtension
*/
private bool $bIsMarkedAsModified = false;
/**
* Constructor from a row of data (as a hash 'attcode' => value)
@@ -750,10 +759,10 @@ HTML
$oPage->SetCurrentTab($sTabCode, $oAttDef->GetLabel().$sCount, $sTabDescription);
$aArgs = array('this' => $this);
$sEditWhen = $oAttDef->GetEditWhen();
// Calculate if edit_when allows to edit based on current $bEditMode
$bIsEditableBasedOnEditWhen = ($sEditWhen === LINKSET_EDITWHEN_ALWAYS) ||
$bIsEditableBasedOnEditWhen = ($sEditWhen === LINKSET_EDITWHEN_ALWAYS) ||
($bEditMode ? $sEditWhen === LINKSET_EDITWHEN_ON_HOST_EDITION : $sEditWhen === LINKSET_EDITWHEN_ON_HOST_DISPLAY);
$bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)) || !$bIsEditableBasedOnEditWhen;
@@ -1209,35 +1218,6 @@ HTML
return DataTableUIBlockFactory::MakeForObject($oPage, $sTableId, $oSet, $aExtraParams);
}
/**
* Get the HTML fragment corresponding to the display of a table representing a set of objects
*
* @param WebPage $oPage The page object is used for out-of-band information (mostly scripts) output
* @param \DBObjectSet $oSet The set of objects to display
* @param array $aExtraParams key used :
* <ul>
* <li>view_link : if true then for extkey will display links with friendly name and make column sortable, default true
* <li>menu : if true prints DisplayBlock menu, default true
* <li>display_aliases : list of query aliases that will be printed, defaults to [] (displays all)
* <li>zlist : name of the zlist to use, false to disable zlist lookup, default to 'list'
* <li>extra_fields : list of <alias>.<attcode> to add to the result, separator ',', defaults to empty string
* </ul>
*
* @return String The HTML fragment representing the table of objects. <b>Warning</b> : no JS added to handled
* pagination or table sorting !
*
* @see DisplayBlock to get a similar table but with the JS for pagination & sorting
*
* @deprecated 3.0.0 use GetDisplaySetBlock
*/
public static function GetDisplaySet(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use GetDisplaySetBlock');
$oPage->AddUiBlock(static::GetDisplaySetBlock($oPage, $oSet, $aExtraParams));
return "";
}
/**
* @param WebPage $oPage
* @param \DBObjectSet $oSet
@@ -1411,129 +1391,6 @@ HTML
//DataTableUIBlockFactory::MakeForStaticData('', $aHeader, $aRows);
}
/**
* @param WebPage $oPage
* @param \CMDBObjectSet $oSet
* @param array $aExtraParams key used :
* <ul>
* <li>view_link : if true then for extkey will display links with friendly name and make column sortable, default true
* <li>menu : if true prints DisplayBlock menu, default true
* <li>display_aliases : list of query aliases that will be printed, defaults to [] (displays all)
* <li>zlist : name of the zlist to use, false to disable zlist lookup, default to 'list'
* <li>extra_fields : list of <alias>.<attcode> to add to the result, separator ',', defaults to empty string
* </ul>
*
* @return string
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @deprecated 3.0.0
*/
public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
if (empty($aExtraParams['currentId'])) {
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
} else {
$iListId = $aExtraParams['currentId'];
}
$aList = array();
// Initialize and check the parameters
$bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true;
$bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true;
// Check if there is a list of aliases to limit the display to...
$aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',',
$aExtraParams['display_aliases']) : array();
$sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list';
$aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',',
trim($aExtraParams['extra_fields'])) : array();
$aExtraFields = array();
$sAttCode = '';
foreach($aExtraFieldsRaw as $sFieldName)
{
// Ignore attributes not of the main queried class
if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches))
{
$sClassAlias = $aMatches[1];
$sAttCode = $aMatches[2];
if (array_key_exists($sClassAlias, $oSet->GetSelectedClasses()))
{
$aExtraFields[$sClassAlias][] = $sAttCode;
}
}
else
{
$aExtraFields['*'] = $sAttCode;
}
}
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$aAuthorizedClasses = array();
foreach($aClasses as $sAlias => $sClassName)
{
if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) &&
((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases))))
{
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
if (array_key_exists($sAlias, $aExtraFields))
{
$aList[$sAlias] = $aExtraFields[$sAlias];
}
else
{
$aList[$sAlias] = array();
}
if ($sZListName !== false)
{
$aDefaultList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
$aList[$sAlias] = array_merge($aDefaultList, $aList[$sAlias]);
}
// Filter the list to removed linked set since we are not able to display them here
foreach ($aList[$sAlias] as $index => $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
if ($oAttDef instanceof AttributeLinkedSet)
{
// Removed from the display list
unset($aList[$sAlias][$index]);
}
}
if (empty($aList[$sAlias]))
{
unset($aList[$sAlias], $aAuthorizedClasses[$sAlias]);
}
}
$sSelectMode = 'none';
$oDataTable = new DataTable($iListId, $oSet, $aAuthorizedClasses);
$oSettings = DataTableSettings::GetDataModelSettings($aAuthorizedClasses, $bViewLink, $aList);
$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
if ($bDisplayLimit)
{
$iDefaultPageSize = appUserPreferences::GetPref('default_page_size',
MetaModel::GetConfig()->GetMinDisplayLimit());
$oSettings->iDefaultPageSize = $iDefaultPageSize;
}
$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams);
}
/**
* @param WebPage $oPage
* @param \CMDBObjectSet $oSet
@@ -1722,7 +1579,7 @@ HTML
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*
*
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
*/
public static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array())
@@ -2223,7 +2080,7 @@ JS
);
// test query link
$sTestResId = 'query_res_'.$sFieldPrefix.$sAttCode.$sNameSuffix; //$oPage->GetUniqueId();
$sTestResId = 'query_res_'.$sFieldPrefix.$sAttCode.$sNameSuffix;
$sBaseUrl = utils::GetAbsoluteUrlAppRoot().'pages/run_query.php?expression=';
$sTestQueryLbl = Dict::S('UI:Edit:TestQuery');
$oTestQueryButton = ButtonUIBlockFactory::MakeIconAction(
@@ -2673,7 +2530,7 @@ HTML;
}
break;
}
$sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$';
$sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$';
if (!empty($aEventsList))
{
if (!is_numeric($sNullValue))
@@ -3584,15 +3441,13 @@ EOF
} else {
//we can directly apply the stimuli
$sExceptionMessage = null;
$bApplyStimulus = false;
try {
$bApplyStimulus = $this->ApplyStimulus($sStimulus); // will write the object in the DB
}
catch (Exception $oException) {
$bApplyStimulus = $this->ApplyStimulus($sStimulus); // will write the object in the DB
} catch (Exception $oException) {
// Catch any exception happening during the stimulus
$bApplyStimulus = false;
$sExceptionMessage = ($oException instanceof CoreCannotSaveObjectException) ? $oException->getHtmlMessage() : $oException->getMessage();
}
finally {
} finally {
if ($sOwnershipToken !== null) {
// Release the concurrent lock, if any
iTopOwnershipLock::ReleaseLock($sClass, $iKey, $sOwnershipToken);
@@ -3600,9 +3455,8 @@ EOF
if (!$bApplyStimulus) {
// Throw an application oriented exception if necessary
throw new ApplicationException($sExceptionMessage ?? Dict::S('UI:FailedToApplyStimuli'));
} else {
return true;
}
return true;
}
}
@@ -3842,7 +3696,7 @@ HTML;
public function GetHilightClass()
{
// Possible return values are:
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
$current = parent::GetHilightClass(); // Default computation
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
@@ -4542,21 +4396,6 @@ HTML;
return $res;
}
protected function PostInsertActions(): void
{
parent::PostInsertActions();
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
}
/**
* @inheritdoc
* Attaches InlineImages to the current object
@@ -4589,21 +4428,6 @@ HTML;
return $res;
}
protected function PostUpdateActions(array $aChanges): void
{
parent::PostUpdateActions($aChanges);
// Invoke extensions after the update (could be before)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBUpdate()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
}
}
/**
* @param string $sMessageIdPrefix
*
@@ -4639,21 +4463,6 @@ HTML;
return $oDeletionPlan;
}
final protected function PreDeleteActions(): void
{
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
}
parent::PreDeleteActions();
}
final protected function PostDeleteActions(): void
{
parent::PostDeleteActions();
@@ -4666,25 +4475,20 @@ HTML;
return true;
}
// Plugins
//
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified()");
$oKPI = new ExecutionKPI();
$bIsModified = $oExtensionInstance->OnIsModified($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
if ($bIsModified) {
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> true");
return true;
} else {
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> false");
}
}
return $this->bIsMarkedAsModified;
}
return false;
/**
* Override the default modification state of the object.
*
* The object is considered as modified, whatever the actual state is.
* This is used when an object is modified indirectly (eg. through a linked set)
*
* @return void
*/
public function MarkObjectAsModified(): void
{
$this->bIsMarkedAsModified = true;
}
/**
@@ -4698,7 +4502,7 @@ HTML;
}
/**
* Whether to bypass the checks of user rights when writing this object, could be used in {@link \iApplicationObjectExtension::OnCheckToWrite()}
* Whether to bypass the checks of user rights when writing this object
*
* @return bool
*/
@@ -4727,22 +4531,6 @@ HTML;
{
parent::DoCheckToWrite();
// Plugins
//
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToWrite()");
$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);
}
}
// User rights
//
if (!$this->bAllowWrite)
@@ -4779,22 +4567,6 @@ HTML;
{
parent::DoCheckToDelete($oDeletionPlan);
// Plugins
//
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToDelete()");
$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);
}
}
// User rights
//
if (! $this->bAllowDelete)
@@ -4874,66 +4646,6 @@ HTML;
}
}
/**
* @param $sCurrentState
* @param $sStimulus
* @param $bOnlyNewOnes
*
* @return array
* @throws \ApplicationException
* @throws \CoreException
* @deprecated Since iTop 2.4, use DBObject::GetTransitionAttributes() instead.
*/
public function GetExpectedAttributes($sCurrentState, $sStimulus, $bOnlyNewOnes)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('Since iTop 2.4, use DBObject::GetTransitionAttributes() instead');
$aTransitions = $this->EnumTransitions();
if (!isset($aTransitions[$sStimulus])) {
// Invalid stimulus
throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus,
$this->GetName(), $this->GetStateLabel()));
}
$aTransition = $aTransitions[$sStimulus];
$sTargetState = $aTransition['target_state'];
$aTargetStates = MetaModel::EnumStates(get_class($this));
$aTargetState = $aTargetStates[$sTargetState];
$aCurrentState = $aTargetStates[$this->GetState()];
$aExpectedAttributes = $aTargetState['attribute_list'];
$aCurrentAttributes = $aCurrentState['attribute_list'];
$aComputedAttributes = array();
foreach($aExpectedAttributes as $sAttCode => $iExpectCode)
{
if (!array_key_exists($sAttCode, $aCurrentAttributes))
{
$aComputedAttributes[$sAttCode] = $iExpectCode;
}
else
{
if (!($aCurrentAttributes[$sAttCode] & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)))
{
$iExpectCode = $iExpectCode & ~(OPT_ATT_MUSTPROMPT | OPT_ATT_MUSTCHANGE); // Already prompted/changed, reset the flags
}
// Later: better check if the attribute is not *null*
if (($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) != ''))
{
$iExpectCode = $iExpectCode & ~(OPT_ATT_MANDATORY); // If the attribute is present, then no need to request its presence
}
$aComputedAttributes[$sAttCode] = $iExpectCode;
}
$aComputedAttributes[$sAttCode] = $aComputedAttributes[$sAttCode] & ~(OPT_ATT_READONLY | OPT_ATT_HIDDEN); // Don't care about this form now
if ($aComputedAttributes[$sAttCode] == 0)
{
unset($aComputedAttributes[$sAttCode]);
}
}
return $aComputedAttributes;
}
/**
* Display a form for modifying several objects at once
* The form will be submitted to the current page, with the specified additional values
@@ -5809,7 +5521,7 @@ JS
{
$this->NotifyAttachedObjectsOnLinkClassModification();
$this->RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey());
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges, 'stimulus_applied' => $sStimulusBeingApplied]);
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges, 'stimulus_applied' => $sStimulusBeingApplied, 'cmdb_change' => self::GetCurrentChange()]);
}
//////////////
@@ -5847,7 +5559,7 @@ JS
final protected function FireEventAfterDelete(): void
{
$this->NotifyAttachedObjectsOnLinkClassModification();
$this->FireEvent(EVENT_DB_AFTER_DELETE);
$this->FireEvent(EVENT_DB_AFTER_DELETE, ['cmdb_change' => self::GetCurrentChange()]);
}
/**

View File

@@ -419,7 +419,7 @@ class DisplayBlock
$oSet->OptimizeColumnLoad(array($oSet->GetClassAlias() => array())); // No need to load all the columns just to get the id
while($oObject = $oSet->Fetch())
{
$aKeys[] = $oObject->GetKey();
$aKeys[] = $oObject->GetKey();
}
$oSet->Rewind();
if (count($aKeys) > 0)
@@ -772,10 +772,6 @@ class DisplayBlock
$oBlock = $this->RenderList($aExtraParams, $oPage);
break;
case 'links':
$oBlock = $this->RenderLinks($oPage, $aExtraParams);
break;
case static::ENUM_STYLE_ACTIONS:
$oBlock = $this->RenderActions($aExtraParams);
break;
@@ -795,11 +791,11 @@ class DisplayBlock
case static::ENUM_STYLE_CHART:
$oBlock = $this->RenderChart($sId, $aQueryParams, $aExtraParams);
break;
case static::ENUM_STYLE_CHART_AJAX:
$oBlock = $this->RenderChartAjax($aExtraParams);
break;
default:
// Unsupported style, do nothing.
$sHtml .= Dict::format('UI:Error:UnsupportedStyleOfBlock', $this->m_sStyle);
@@ -897,7 +893,7 @@ JS
$sClass = $this->m_oFilter->GetClass();
$bConditionAdded = false;
// If the condition is an external key with a class having a hierarchy, use a "below" criteria
if (MetaModel::IsValidAttCode($sClass, $sFilterCode))
{
@@ -906,13 +902,13 @@ JS
if ($oAttDef->IsExternalKey())
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass());
if ($sHierarchicalKeyCode !== false)
{
$oFilter = new DBObjectSearch($oAttDef->GetTargetClass());
if (($sOpCode == 'IN') && is_array($condition))
{
$oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition));
$oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition));
}
else
{
@@ -935,21 +931,21 @@ JS
$bConditionAdded = true;
}
}
// In all other cases, just add the condition directly
if (!$bConditionAdded)
{
$this->m_oFilter->AddCondition($sFilterCode, $condition, null); // Use the default 'loose' operator
}
}
static protected function GetConditionIN($oFilter, $sFilterCode, $condition)
{
$oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias());
$sListExpr = '('.implode(', ', CMDBSource::Quote($condition)).')';
$sOQLCondition = $oField->RenderExpression()." IN $sListExpr";
$oNewCondition = Expression::FromOQL($sOQLCondition);
return $oNewCondition;
return $oNewCondition;
}
/**
@@ -1565,54 +1561,6 @@ JS
return $oContentBlock;
}
/**
* @deprecated 3.1.0 N°5957
*
* @param WebPage $oPage
* @param array $aExtraParams
*
* @return \Combodo\iTop\Application\UI\Base\Component\Html\Html|\Combodo\iTop\Application\UI\Base\Layout\UIContentBlock|string
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \ReflectionException
*/
protected function RenderLinks(WebPage $oPage, array $aExtraParams)
{
// Note: No deprecation ticket yet as we want to wait and see if people / code actually use this method, in which case we might keep it.
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('This method is most likely not used throughout the application and will be removed soon. If you ever see this message, please inform us.');
$oBlock = null;
if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) {
$oBlock = cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams);
} else {
$sClass = $this->m_oFilter->GetClass();
$oAttDef = MetaModel::GetAttributeDef($sClass, $this->m_aParams['target_attr']);
$sTargetClass = $oAttDef->GetTargetClass();
$oBlock = new Html('<p>'.Dict::Format('UI:NoObject_Class_ToDisplay', MetaModel::GetName($sTargetClass)).'</p>');
$bDisplayMenu = isset($this->m_aParams['menu']) ? $this->m_aParams['menu'] == true : true;
if ($bDisplayMenu) {
if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) {
$sDefaults = '';
if (isset($this->m_aParams['default'])) {
foreach ($this->m_aParams['default'] as $sName => $sValue) {
$sDefaults .= '&'.urlencode($sName).'='.urlencode($sValue);
}
}
$oBlock->AddHtml("<p><a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=modify_links&class=$sClass&sParams&link_attr=".$aExtraParams['link_attr']."&id=".$aExtraParams['object_id']."&target_class=$sTargetClass&addObjects=true$sDefaults\">".Dict::Format('UI:ClickToCreateNew',
Metamodel::GetName($sClass))."</a></p>\n");
}
}
}
return $oBlock;
}
/**
* @param string|null $sChartId
* @param array $aQueryParams

View File

@@ -358,50 +358,6 @@ class ApplicationMenu
return $aSubMenuNodes;
}
/**
* Entry point to display the whole menu into the web page, used by iTopWebPage
* @param WebPage $oPage
* @param array $aExtraParams
* @throws DictExceptionMissingString
*
* @deprecated Will be removed in 3.0.0, use static::GetMenuGroups() instead
*/
public static function DisplayMenu($oPage, $aExtraParams)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use static::GetMenuGroups() instead');
self::LoadAdditionalMenus();
// Sort the root menu based on the rank
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$iAccordion = 0;
$iActiveAccordion = $iAccordion;
$iActiveMenu = self::GetMenuIndexById(self::GetActiveNodeId());
foreach (self::$aRootMenus as $aMenu) {
if (!self::CanDisplayMenu($aMenu)) {
continue;
}
$oMenuNode = self::GetMenuNode($aMenu['index']);
$oPage->AddToMenu('<h3 id="'.utils::GetSafeId('AccordionMenu_'.$oMenuNode->GetMenuID()).'" class="navigation-menu-group" data-menu-id="'.$oMenuNode->GetMenuId().'">'.$oMenuNode->GetTitle().'</h3>');
$oPage->AddToMenu('<div>');
$oPage->AddToMenu('<ul>');
$aChildren = self::GetChildren($aMenu['index']);
$bActive = self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu);
$oPage->AddToMenu('</ul>');
if ($bActive)
{
$iActiveAccordion = $iAccordion;
}
$oPage->AddToMenu('</div>');
$iAccordion++;
}
$oPage->add_ready_script(
<<<EOF
// Accordion Menu
$("#accordion").css({display:'block'}).accordion({ header: "h3", heightStyle: "content", collapsible: true, active: $iActiveAccordion, icons: false, animate: true }); // collapsible will be enabled once the item will be selected
EOF
);
}
/**
* Recursively check if the menu and at least one of his sub-menu is enabled
* @param array $aMenu menu entry
@@ -431,73 +387,6 @@ EOF
return false;
}
/**
* Handles the display of the sub-menus (called recursively if necessary)
*
* @param WebPage $oPage
* @param array $aMenus
* @param array $aExtraParams
* @param int $iActiveMenu
*
* @return bool True if the currently selected menu is one of the submenus
* @throws DictExceptionMissingString
* @throws \Exception
* @deprecated Will be removed in 3.0.0, use static::GetSubMenuNodes() instead
*/
protected static function DisplaySubMenu($oPage, $aMenus, $aExtraParams, $iActiveMenu = -1)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use static::GetSubMenuNodes() instead');
// Sort the menu based on the rank
$bActive = false;
usort($aMenus, array('ApplicationMenu', 'CompareOnRank'));
foreach ($aMenus as $aMenu) {
if (!self::CanDisplayMenu($aMenu)) {
continue;
}
$index = $aMenu['index'];
$oMenu = self::GetMenuNode($index);
if ($oMenu->IsEnabled())
{
$aChildren = self::GetChildren($index);
$aCSSClasses = array('navigation-menu-item');
if (count($aChildren) > 0)
{
$aCSSClasses[] = 'submenu';
}
$sHyperlink = $oMenu->GetHyperlink($aExtraParams);
$sItemHtml = '<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'" class="'.implode(' ', $aCSSClasses).'" data-menu-id="'.$oMenu->GetMenuID().'">';
if ($sHyperlink != '')
{
$sLinkTarget = '';
if ($oMenu->IsHyperLinkInNewWindow())
{
$sLinkTarget .= ' target="_blank"';
}
$sURL = '"'.$oMenu->GetHyperlink($aExtraParams).'"'.$sLinkTarget;
$sTitle = utils::HtmlEntities($oMenu->GetTitle());
$sItemHtml .= "<a href={$sURL}>{$sTitle}</a>";
}
else
{
$sItemHtml .= $oMenu->GetTitle();
}
$sItemHtml .= '</li>';
$oPage->AddToMenu($sItemHtml);
if ($iActiveMenu == $index)
{
$bActive = true;
}
if (count($aChildren) > 0)
{
$oPage->AddToMenu('<ul>');
$bActive |= self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu);
$oPage->AddToMenu('</ul>');
}
}
}
return $bActive;
}
/**
* Helper function to sort the menus based on their rank
* @param array $a

View File

@@ -2424,19 +2424,6 @@ SQL;
return $bRet;
}
/**
* @param $sPath
*
* @return false|\ormDocument
* @throws \Exception
*
* @deprecated 3.2.1 use utils::GetDocumentFromSelfURL instead
*/
public static function IsSelfURL($sPath)
{
return self::GetDocumentFromSelfURL($sPath);
}
/**
* 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

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.
@@ -31,7 +31,7 @@ require_once(APPROOT.'/application/uiwizard.class.inc.php');
class WizardHelper
{
protected $m_aData;
public function __construct()
{
}
@@ -39,7 +39,7 @@ class WizardHelper
* Constructs the PHP target object from the parameters sent to the web page by the wizard
* @param boolean $bReadUploadedFiles True to also read any uploaded file (for blob/document fields)
* @return object
*/
*/
public function GetTargetObject($bReadUploadedFiles = false)
{
if (isset($this->m_aData['m_oCurrentValues']['id']))
@@ -207,7 +207,7 @@ class WizardHelper
else
{
$oObj->Set($sAttCode, $value);
}
}
}
}
if (isset($this->m_aData['m_sState']) && !empty($this->m_aData['m_sState']))
@@ -217,12 +217,12 @@ class WizardHelper
$oObj->DoComputeValues();
return $oObj;
}
public function GetFieldsForDefaultValue()
{
return $this->m_aData['m_aDefaultValueRequested'];
}
public function SetDefaultValue($sAttCode, $value)
{
// Protect against a request for a non existing field
@@ -247,7 +247,7 @@ class WizardHelper
$aData[] = $aRow;
}
$this->m_aData['m_oDefaultValue'][$sAttCode] = json_encode($aData);
}
else
{
@@ -256,12 +256,12 @@ class WizardHelper
}
}
}
public function GetFieldsForAllowedValues()
{
return $this->m_aData['m_aAllowedValuesRequested'];
}
public function SetAllowedValuesHtml($sAttCode, $sHtml)
{
// Protect against a request for a non existing field
@@ -270,12 +270,12 @@ class WizardHelper
$this->m_aData['m_oAllowedValues'][$sAttCode] = $sHtml;
}
}
public function ToJSON()
{
return json_encode($this->m_aData);
}
static public function FromJSON($sJSON)
{
$oWizHelper = new WizardHelper();
@@ -283,7 +283,7 @@ class WizardHelper
$oWizHelper->m_aData = $aData;
return $oWizHelper;
}
protected function GetLinkedWizardStructure($oAttDef)
{
$oWizard = new UIWizard(null, $oAttDef->GetLinkedClass());
@@ -310,7 +310,7 @@ class WizardHelper
}
return $aFields;
}
public function GetTargetClass()
{
return $this->m_aData['m_sClass'];
@@ -330,7 +330,7 @@ class WizardHelper
{
return isset($this->m_aData['m_sStimulus']) ? $this->m_aData['m_sStimulus'] : null;
}
public function GetIdForField($sFieldName)
{
$sResult = '';
@@ -375,31 +375,4 @@ JS
);
}
/*
* Function with an old pattern of code
* @deprecated 3.1.0
*/
static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet)
{
$aSet = json_decode($sJsonSet, true); // true means hash array instead of object
$oSet = CMDBObjectSet::FromScratch($sLinkClass);
foreach ($aSet as $aLinkObj) {
$oLink = MetaModel::NewObject($sLinkClass);
foreach ($aLinkObj as $sAttCode => $value) {
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0))
{
// For external keys: load the target object so that external fields
// get filled too
$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value);
$oLink->Set($sAttCode, $oTargetObj);
}
$oLink->Set($sAttCode, $value);
}
$oLink->Set($sExtKeyToMe, $oMe->GetKey());
$oSet->AddObject($oLink);
}
return $oSet;
}
}

View File

@@ -15,32 +15,34 @@
"apereo/phpcas": "~1.6.0",
"firebase/php-jwt": "^6.4.0",
"guzzlehttp/guzzle": "^7.5.1",
"laminas/laminas-mail": "^2.11",
"laminas/laminas-servicemanager": "^3.5",
"league/oauth2-google": "^4.0.1",
"nikic/php-parser": "~5.6.0",
"nikic/php-parser": "dev-master",
"pear/archive_tar": "~1.4.14",
"pelago/emogrifier": "^7.2.0",
"psr/log": "^3.0.0",
"scssphp/scssphp": "^1.12.1",
"soundasleep/html2text": "~2.1",
"symfony/console": "~6.4.0",
"symfony/dotenv": "~6.4.0",
"symfony/form": "^6.4",
"symfony/framework-bundle": "~6.4.0",
"symfony/http-foundation": "~6.4.0",
"symfony/http-kernel": "~6.4.0",
"symfony/runtime": "~6.4.0",
"symfony/mailer": "^6.4",
"symfony/security-csrf": "^6.4",
"symfony/twig-bundle": "~6.4.0",
"symfony/var-dumper": "~6.4.0",
"symfony/yaml": "~6.4.0",
"tecnickcom/tcpdf": "^6.6.0",
"thenetworg/oauth2-azure": "^2.0",
"soundasleep/html2text": "~2.1"
"thenetworg/oauth2-azure": "^2.0"
},
"require-dev": {
"symfony/debug-bundle": "~6.4.0",
"symfony/stopwatch": "~6.4.0",
"symfony/web-profiler-bundle": "~6.4.0"
},
"repositories": [{
"type": "vcs",
"url": "https://github.com/Combodo/PHP-Parser"
}],
"suggest": {
"ext-libsodium": "Required to use the AttributeEncryptedString.",
"ext-openssl": "Can be used as a polyfill if libsodium is not installed",
@@ -60,10 +62,7 @@
},
"sort-packages": true,
"classmap-authoritative": true,
"platform-check": true,
"allow-plugins": {
"symfony/runtime": true
}
"platform-check": true
},
"autoload": {
"classmap": [
@@ -91,7 +90,7 @@
"extra": {
"symfony": {
"allow-contrib": false,
"require": "3.4.*"
"require": "6.4.*"
},
"runtime": {
"dotenv_path": "resources/symfony/.env"

2378
composer.lock generated

File diff suppressed because it is too large Load Diff

938
core/action.class.inc.php Normal file
View File

@@ -0,0 +1,938 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// 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.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Service\Notification\NotificationsRepository;
use Combodo\iTop\Service\Notification\NotificationsService;
use Combodo\iTop\Service\Router\Router;
/**
* Persistent classes (internal): user defined actions
*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/core/asynctask.class.inc.php');
require_once(APPROOT.'/core/email.class.inc.php');
/**
* A user defined action, to customize the application
*
* @package iTopORM
*/
abstract class Action extends cmdbAbstractObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "name",
"complementary_name_attcode" => ['finalclass', 'description'],
"state_attcode" => "status",
"reconc_keys" => ['name'],
"db_table" => "priv_action",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"style" => new ormStyle("ibo-dm-class--Action", "ibo-dm-class-alt--Action", "var(--ibo-dm-class--Action--main-color)", "var(--ibo-dm-class--Action--complementary-color)", null, '../images/icons/icons8-in-transit.svg'),
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values" => null, "sql" => "name", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array(
"allowed_values" => new ValueSetEnum(array('test' => 'Being tested', 'enabled' => 'In production', 'disabled' => 'Inactive')),
"styled_values" => [
'test' => new ormStyle('ibo-dm-enum--Action-status-test', 'ibo-dm-enum-alt--Action-status-test', 'var(--ibo-dm-enum--Action-status-test--main-color)', 'var(--ibo-dm-enum--Action-status-test--complementary-color)', null, null),
'enabled' => new ormStyle('ibo-dm-enum--Action-status-enabled', 'ibo-dm-enum-alt--Action-status-enabled', 'var(--ibo-dm-enum--Action-status-enabled--main-color)', 'var(--ibo-dm-enum--Action-status-enabled--complementary-color)', 'fas fa-check', null),
'disabled' => new ormStyle('ibo-dm-enum--Action-status-disabled', 'ibo-dm-enum-alt--Action-status-disabled', 'var(--ibo-dm-enum--Action-status-disabled--main-color)', 'var(--ibo-dm-enum--Action-status-disabled--complementary-color)', null, null),
],
"display_style" => 'list',
"sql" => "status",
"default_value" => "test",
"is_null_allowed" => false,
"depends_on" => array(),
)));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list",
array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "action_id", "ext_key_to_remote" => "trigger_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array(), "display_style" => 'property')));
MetaModel::Init_AddAttribute(new AttributeEnum("asynchronous", array("allowed_values" => new ValueSetEnum(['use_global_setting' => 'Use global settings','yes' => 'Yes' ,'no' => 'No']), "sql" => "asynchronous", "default_value" => 'use_global_setting', "is_null_allowed" => false, "depends_on" => array())));
// Display lists
// - Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list'));
// - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status'));
// Search criteria
// - Default criteria of the search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'status'));
}
/**
* Encapsulate the execution of the action and handle failure & logging
*
* @param \Trigger $oTrigger
* @param array $aContextArgs
*
* @return mixed
*/
abstract public function DoExecute($oTrigger, $aContextArgs);
/**
* @return bool
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function IsActive()
{
switch($this->Get('status'))
{
case 'enabled':
case 'test':
return true;
default:
return false;
}
}
/**
* Return true if the current action status is set on "test"
*
* @return bool
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function IsBeingTested()
{
switch($this->Get('status'))
{
case 'test':
return true;
default:
return false;
}
}
/**
* @inheritDoc
* @since 3.0.0
*/
public function AfterInsert()
{
parent::AfterInsert();
$this->DoCheckIfHasTrigger();
}
/**
* @inheritDoc
* @since 3.0.0
*/
public function AfterUpdate()
{
parent::AfterUpdate();
$this->DoCheckIfHasTrigger();
}
/**
* Check if the Action has at least 1 trigger linked. Otherwise, it adds a warning.
* @return void
* @since 3.0.0
*/
protected function DoCheckIfHasTrigger()
{
$oTriggersSet = $this->Get('trigger_list');
if ($oTriggersSet->Count() === 0) {
$this->m_aCheckWarnings[] = Dict::S('Action:WarningNoTriggerLinked');
}
}
/**
* @since 3.2.0 N°5472 method creation
*/
public function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, false);
if ($oPage instanceof iTopWebPage && !$this->IsNew()) {
$this->GenerateLastExecutionsTab($oPage, $bEditMode);
}
}
/**
* @since 3.2.0 N°5472 method creation
*/
protected function GenerateLastExecutionsTab(iTopWebPage $oPage, $bEditMode)
{
$oRouter = Router::GetInstance();
$sActionLastExecutionsPageUrl = $oRouter->GenerateUrl('notifications.action.last_executions_tab', ['action_id' => $this->GetKey()]);
$oPage->AddAjaxTab('action_errors', $sActionLastExecutionsPageUrl, false, Dict::S('Action:last_executions_tab'));
}
/**
* @param \Combodo\iTop\Application\WebPage\WebPage $oPage
*
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \ConfigException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \InvalidConfigParamException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \ReflectionException
* @since 3.2.0 N°5472 method creation
*/
public function GetLastExecutionsTabContent(WebPage $oPage): void
{
$oConfig = utils::GetConfig();
$sLastExecutionDaysConfigParamName = 'notifications.last_executions_days';
$iLastExecutionDays = $oConfig->Get($sLastExecutionDaysConfigParamName);
if ($iLastExecutionDays < 0) {
throw new InvalidConfigParamException("Invalid value for {$sLastExecutionDaysConfigParamName} config parameter. Param desc: " . $oConfig->GetDescription($sLastExecutionDaysConfigParamName));
}
$sActionQueryOql = 'SELECT EventNotification WHERE action_id = :action_id';
$aActionQueryParams = ['action_id' => $this->GetKey()];
if ($iLastExecutionDays > 0) {
$sActionQueryOql .= ' AND date > DATE_SUB(NOW(), INTERVAL :days DAY)';
$aActionQueryParams['days'] = $iLastExecutionDays;
$sActionQueryLimit = Dict::Format('Action:last_executions_tab_limit_days', $iLastExecutionDays);
} else {
$sActionQueryLimit = Dict::S('Action:last_executions_tab_limit_none');
}
$oActionFilter = DBObjectSearch::FromOQL($sActionQueryOql, $aActionQueryParams);
$oSet = new DBObjectSet($oActionFilter, ['date' => false]);
$sPanelTitle = Dict::Format('Action:last_executions_tab_panel_title', $sActionQueryLimit);
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle]);
$oPage->AddUiBlock($oExecutionsListBlock);
}
/**
* Will be overloaded by the children classes to return the value of their global asynchronous setting (eg. `email_asynchronous` for `\ActionEmail`, `prefer_asynchronous` for `\ActionWebhook`, ...)
*
* @return bool true if the global setting for this kind of action if to be executed asynchronously, false otherwise.
* @since 3.2.0
*/
public static function GetAsynchronousGlobalSetting(): bool
{
return false;
}
/**
* @return bool true if that action instance should be executed asynchronously, otherwise false
* @throws \ArchivedObjectException
* @throws \CoreException
* @since 3.2.0
*/
public function IsAsynchronous(): bool
{
$sAsynchronous = $this->Get('asynchronous');
if ($sAsynchronous === 'use_global_setting') {
return static::GetAsynchronousGlobalSetting();
}
return $sAsynchronous === 'yes';
}
}
/**
* A notification
*
* @package iTopORM
*/
abstract class ActionNotification extends Action
{
/**
* @inheritDoc
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "name",
"complementary_name_attcode" => ['finalclass', 'description'],
"state_attcode" => "",
"reconc_keys" => ['name'],
"db_table" => "priv_action_notification",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
// - Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list'));
// - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('finalclass', 'description', 'status'));
MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Search criteria
// - Criteria of the std search form
// MetaModel::Init_SetZListItems('standard_search', array('name'));
// - Default criteria of the search form
// MetaModel::Init_SetZListItems('default_search', array('name'));
}
/**
* @param $sLanguage
* @param $sLanguageCode
*
* @return array [$sPreviousLanguage, $aPreviousPluginProperties]
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionUnknownLanguage
* @since 3.2.0
*/
public function SetNotificationLanguage($sLanguage = null, $sLanguageCode = null){
$sPreviousLanguage = Dict::GetUserLanguage();
$aPreviousPluginProperties = ApplicationContext::GetPluginProperties('QueryLocalizerPlugin');
$sLanguage = $sLanguage ?? $this->Get('language');
$sLanguageCode = $sLanguageCode ?? $sLanguage;
if (!utils::IsNullOrEmptyString($sLanguage)) {
// If a language is specified for this action, force this language
// when rendering all placeholders inside this message
Dict::SetUserLanguage($sLanguage);
AttributeDateTime::LoadFormatFromConfig();
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', $sLanguageCode);
}
return [$sPreviousLanguage, $aPreviousPluginProperties];
}
}
/**
* An email notification
*
* @package iTopORM
*/
class ActionEmail extends ActionNotification
{
/**
* @var string
* @since 3.0.1
*/
const ENUM_HEADER_NAME_MESSAGE_ID = 'Message-ID';
/**
* @var string
* @since 3.0.1
*/
const ENUM_HEADER_NAME_REFERENCES = 'References';
/**
* @var string
* @since 3.1.0
*/
const TEMPLATE_BODY_CONTENT = '$content$';
/**
* Wraps the 'body' of the message for previewing inside an IFRAME -- i.e. without any of the iTop stylesheets being applied
* @var string
* @since 3.1.0
*/
const CONTENT_HIGHLIGHT = '<div style="border:2px dashed #6800ff;position:relative;padding:2px;margin-top:14px;"><div style="background-color:#6800ff;color:#fff;font-family:Courier New, sans-serif;font-size:14px;line-height:16px;padding:3px;display:block;position:absolute;top:-22px;right:0;">$content$</div>%s</div>';
/**
* Wraps a placeholder of the email's body for previewing inside an IFRAME -- i.e. without any of the iTop stylesheets being applied
* @var string
*/
const FIELD_HIGHLIGHT = '<span style="background-color:#6800ff;color:#fff;font-size:smaller;font-family:Courier New, sans-serif;padding:2px;">\\$$1\\$</span>';
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_action_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-mailing.svg'),
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("allowed_values" => null, "sql" => "test_recipient", "default_value" => "", "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("from", array("allowed_values" => null, "sql" => "from", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("from_label", array("allowed_values" => null, "sql" => "from_label", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("allowed_values" => null, "sql" => "reply_to", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("reply_to_label", array("allowed_values" => null, "sql" => "reply_to_label", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeOQL("to", array("allowed_values" => null, "sql" => "to", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values" => null, "sql" => "cc", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values" => null, "sql" => "bcc", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values" => null, "sql" => "subject", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values" => null, "sql" => "body", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values" => new ValueSetEnum('low,normal,high'), "sql" => "importance", "default_value" => 'normal', "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeBlob("html_template", array("is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeEnum("ignore_notify", array("allowed_values" => new ValueSetEnum('yes,no'), "sql" => "ignore_notify", "default_value" => 'yes', "is_null_allowed" => false, "depends_on" => array())));
// Display lists
// - Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array(
'col:col1' => array(
'fieldset:ActionEmail:main' => array(
0 => 'name',
1 => 'description',
2 => 'status',
3 => 'language',
4 => 'html_template',
5 => 'subject',
6 => 'body',
// 5 => 'importance', not handled when sending the mail, better hide it then
),
'fieldset:ActionEmail:trigger' => array(
0 => 'trigger_list',
1 => 'asynchronous'
),
),
'col:col2' => array(
'fieldset:ActionEmail:recipients' => array(
0 => 'from',
1 => 'from_label',
2 => 'reply_to',
3 => 'reply_to_label',
4 => 'test_recipient',
5 => 'ignore_notify',
6 => 'to',
7 => 'cc',
8 => 'bcc',
),
),
));
// - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('status', 'to', 'subject', 'language'));
// Search criteria
// - Standard criteria of the search
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'status', 'subject', 'language'));
// - Default criteria for the search
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'status', 'subject', 'language'));
}
// count the recipients found
protected $m_iRecipients;
// Errors management : not that simple because we need that function to be
// executed in the background, while making sure that any issue would be reported clearly
protected $m_aMailErrors; //array of strings explaining the issue
/**
* Return the list of emails as a string, or a detailed error description
*
* @param string $sRecipAttCode
* @param array $aArgs
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
protected function FindRecipients($sRecipAttCode, $aArgs)
{
$oTrigger = $aArgs['trigger->object()'] ?? null;
$sOQL = $this->Get($sRecipAttCode);
if (utils::IsNullOrEmptyString($sOQL)) return '';
try
{
$oSearch = DBObjectSearch::FromOQL($sOQL);
if ($this->Get('ignore_notify') === 'no') {
// In theory, it is possible to notify *any* kind of object,
// as long as there is an email attribute in the class
// So let's not assume that the selected class is a Person
$sFirstSelectedClass = $oSearch->GetClass();
if (MetaModel::IsValidAttCode($sFirstSelectedClass, 'notify')) {
$oSearch->AddCondition('notify', 'yes');
}
}
$oSearch->AllowAllData();
}
catch (OQLException $e)
{
$this->m_aMailErrors[] = "query syntax error for recipient '$sRecipAttCode'";
return $e->getMessage();
}
$sClass = $oSearch->GetClass();
// Determine the email attribute (the first one will be our choice)
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeEmailAddress)
{
$sEmailAttCode = $sAttCode;
// we've got one, exit the loop
break;
}
}
if (!isset($sEmailAttCode))
{
$this->m_aMailErrors[] = "wrong target for recipient '$sRecipAttCode'";
return "The objects of the class '$sClass' do not have any email attribute";
}
if($oTrigger !== null && in_array('Contact', MetaModel::EnumParentClasses($sClass, ENUM_CHILD_CLASSES_ALL), true)) {
$aArgs['trigger_id'] = $oTrigger->GetKey();
$aArgs['action_id'] = $this->GetKey();
$sSubscribedContactsOQL = NotificationsRepository::GetInstance()->GetSearchOQLContactUnsubscribedByTriggerAndAction();
$sSubscribedContactsOQL->ApplyParameters($aArgs);
$sAlias = $oSearch->GetClassAlias();
$oSearch->AddConditionExpression(Expression::FromOQL("`$sAlias`.id NOT IN ($sSubscribedContactsOQL)"));
}
$oSet = new DBObjectSet($oSearch, array() /* order */, $aArgs);
$aRecipients = array();
while ($oObj = $oSet->Fetch())
{
$sAddress = trim($oObj->Get($sEmailAttCode));
if (utils::IsNotNullOrEmptyString($sAddress))
{
$aRecipients[] = $sAddress;
$this->m_iRecipients++;
}
if ($oTrigger !== null && in_array('Contact', MetaModel::EnumParentClasses($sClass, ENUM_CHILD_CLASSES_ALL), true)) {
NotificationsService::GetInstance()->RegisterSubscription($oTrigger, $this, $oObj);
}
}
return implode(', ', $aRecipients);
}
/**
* @inheritDoc
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
*/
public function DoExecute($oTrigger, $aContextArgs)
{
if (MetaModel::IsLogEnabledNotification())
{
$oLog = new EventNotificationEmail();
if ($this->IsBeingTested())
{
$oLog->Set('message', 'TEST - Notification sent ('.$this->Get('test_recipient').')');
}
else
{
$oLog->Set('message', 'Notification pending');
}
$oLog->Set('userinfo', UserRights::GetUser());
$oLog->Set('trigger_id', $oTrigger->GetKey());
$oLog->Set('action_id', $this->GetKey());
$oLog->Set('object_id', $aContextArgs['this->object()']->GetKey());
$oLog->Set('object_class', get_class($aContextArgs['this->object()']));
// Must be inserted now so that it gets a valid id that will make the link
// between an eventual asynchronous task (queued) and the log
$oLog->DBInsertNoReload();
}
else
{
$oLog = null;
}
try
{
$sRes = $this->_DoExecute($oTrigger, $aContextArgs, $oLog);
if ($this->IsBeingTested())
{
$sPrefix = 'TEST ('.$this->Get('test_recipient').') - ';
}
else
{
$sPrefix = '';
}
if ($oLog)
{
$oLog->Set('message', $sPrefix . $sRes);
$oLog->DBUpdate();
}
}
catch (Exception $e)
{
if ($oLog)
{
$oLog->Set('message', 'Error: '.$e->getMessage());
try
{
$oLog->DBUpdate();
}
catch (Exception $eSecondTryUpdate)
{
IssueLog::Error("Failed to process email ".$oLog->GetKey()." - reason: ".$e->getMessage()."\nTrace:\n".$e->getTraceAsString());
$oLog->Set('message', 'Error: more details in the log for email "'.$oLog->GetKey().'"');
$oLog->DBUpdate();
}
}
}
}
/**
* @param \Trigger $oTrigger
* @param array $aContextArgs
* @param \EventNotification $oLog
*
* @return string
* @throws \CoreException
* @throws \Exception
*/
protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
{
$sStyles = file_get_contents(APPROOT . utils::GetCSSFromSASS("css/email.scss"));
$sStyles .= MetaModel::GetConfig()->Get('email_css');
$oEmail = new EMail();
$aEmailContent = $this->PrepareMessageContent($aContextArgs, $oLog);
$oEmail->SetSubject($aEmailContent['subject']);
$oEmail->SetBody($aEmailContent['body'], 'text/html', $sStyles);
$oEmail->SetRecipientTO($aEmailContent['to']);
$oEmail->SetRecipientCC($aEmailContent['cc']);
$oEmail->SetRecipientBCC($aEmailContent['bcc']);
$oEmail->SetRecipientFrom($aEmailContent['from'], $aEmailContent['from_label']);
$oEmail->SetRecipientReplyTo($aEmailContent['reply_to'], $aEmailContent['reply_to_label']);
$oEmail->SetReferences($aEmailContent['references']);
$oEmail->SetMessageId($aEmailContent['message_id']);
$oEmail->SetInReplyTo($aEmailContent['in_reply_to']);
foreach($aEmailContent['attachments'] as $aAttachment) {
$oEmail->AddAttachment($aAttachment['data'], $aAttachment['filename'], $aAttachment['mime_type']);
}
if (empty($this->m_aMailErrors))
{
if ($this->m_iRecipients == 0)
{
return 'No recipient';
}
else
{
$aErrors = [];
$iRes = $oEmail->Send($aErrors, $this->IsAsynchronous() ? Email::ENUM_SEND_FORCE_ASYNCHRONOUS : Email::ENUM_SEND_FORCE_SYNCHRONOUS, $oLog);
switch ($iRes)
{
case EMAIL_SEND_OK:
return "Sent";
case EMAIL_SEND_PENDING:
return "Pending";
case EMAIL_SEND_ERROR:
return "Errors: ".implode(', ', $aErrors);
}
}
} else {
if (is_array($this->m_aMailErrors) && count($this->m_aMailErrors) > 0) {
$sError = implode(', ', $this->m_aMailErrors);
} else {
$sError = 'Unknown reason';
}
return 'Notification was not sent: '.$sError;
}
}
/**
* @param array $aContextArgs
* @param \EventNotification $oLog
*
* @return array
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \DictExceptionMissingString
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @since 3.1.0 N°918
*/
protected function PrepareMessageContent($aContextArgs, &$oLog): array
{
$aMessageContent = [
'to' => '',
'cc' => '',
'bcc' => '',
'from' => '',
'from_label' => '',
'reply_to' => '',
'reply_to_label' => '',
'subject' => '',
'body' => '',
'references' => '',
'message_id' => '',
'in_reply_to' => '',
'attachments' => [],
];
$sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
[$sPreviousLanguage, $aPreviousPluginProperties] = $this->SetNotificationLanguage();
try
{
$this->m_iRecipients = 0;
$this->m_aMailErrors = array();
// Determine recipients
//
$aMessageContent['to'] = $this->FindRecipients('to', $aContextArgs);
$aMessageContent['cc'] = $this->FindRecipients('cc', $aContextArgs);
$aMessageContent['bcc'] = $this->FindRecipients('bcc', $aContextArgs);
$aMessageContent['from'] = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
$aMessageContent['from_label'] = MetaModel::ApplyParams($this->Get('from_label'), $aContextArgs);
$aMessageContent['reply_to'] = MetaModel::ApplyParams($this->Get('reply_to'), $aContextArgs);
$aMessageContent['reply_to_label'] = MetaModel::ApplyParams($this->Get('reply_to_label'), $aContextArgs);
$aMessageContent['subject'] = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
$sBody = $this->BuildMessageBody(false);
$aMessageContent['body'] = MetaModel::ApplyParams($sBody, $aContextArgs);
$oObj = $aContextArgs['this->object()'];
$aMessageContent['message_id'] = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_MESSAGE_ID);
$aMessageContent['references'] = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_REFERENCES);
}
catch (Exception $e) {
/** @noinspection PhpUnhandledExceptionInspection */
throw $e;
}
finally {
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
$this->SetNotificationLanguage($sPreviousLanguage, $aPreviousPluginProperties['language_code'] ?? null);
}
if (!is_null($oLog)) {
// Note: we have to secure this because those values are calculated
// inside the try statement, and we would like to keep track of as
// many data as we could while some variables may still be undefined
if (isset($aMessageContent['to'])) {
$oLog->Set('to', $aMessageContent['to']);
}
if (isset($aMessageContent['cc'])) {
$oLog->Set('cc', $aMessageContent['cc']);
}
if (isset($aMessageContent['bcc'])) {
$oLog->Set('bcc', $aMessageContent['bcc']);
}
if (isset($aMessageContent['from'])) {
$oLog->Set('from', $aMessageContent['from']);
}
if (isset($aMessageContent['subject'])) {
$oLog->Set('subject', $aMessageContent['subject']);
}
if (isset($aMessageContent['body'])) {
$oLog->Set('body', HTMLSanitizer::Sanitize($aMessageContent['body']));
}
}
if ($this->IsBeingTested()) {
$sTestBody = $aMessageContent['body'];
$sTestBody .= "<div style=\"border: dashed;\">\n";
$sTestBody .= "<h1>Testing email notification ".$this->GetHyperlink()."</h1>\n";
$sTestBody .= "<p>The email should be sent with the following properties\n";
$sTestBody .= "<ul>\n";
$sTestBody .= "<li>TO: {$aMessageContent['to']}</li>\n";
$sTestBody .= "<li>CC: {$aMessageContent['cc']}</li>\n";
$sTestBody .= "<li>BCC: {$aMessageContent['bcc']}</li>\n";
$sTestBody .= empty($aMessageContent['from_label']) ? "<li>From: {$aMessageContent['from']}</li>\n" : "<li>From: {$aMessageContent['from_label']} &lt;{$aMessageContent['from']}&gt;</li>\n";
$sTestBody .= empty($aMessageContent['reply_to_label']) ? "<li>Reply-To: {$aMessageContent['reply_to']}</li>\n" : "<li>Reply-To: {$aMessageContent['reply_to_label']} &lt;{$aMessageContent['reply_to']}&gt;</li>\n";
$sTestBody .= "<li>References: {$aMessageContent['references']}</li>\n";
$sTestBody .= "</ul>\n";
$sTestBody .= "</p>\n";
$sTestBody .= "</div>\n";
$aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']';
$aMessageContent['body'] = $sTestBody;
$aMessageContent['to'] = $this->Get('test_recipient');
// N°6677 Ensure emails in test are never sent to cc'd and bcc'd addresses
$aMessageContent['cc'] = '';
$aMessageContent['bcc'] = '';
}
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
$aMessageContent['in_reply_to'] = $aMessageContent['references'];
if (isset($aContextArgs['attachments']))
{
$aAttachmentReport = array();
/** @var \ormDocument $oDocument */
foreach($aContextArgs['attachments'] as $oDocument)
{
$aMessageContent['attachments'][] = ['data' => $oDocument->GetData(), 'filename' => $oDocument->GetFileName(), 'mime_type' => $oDocument->GetMimeType()];
$aAttachmentReport[] = array($oDocument->GetFileName(), $oDocument->GetMimeType(), strlen($oDocument->GetData() ?? ''));
}
$oLog->Set('attachments', $aAttachmentReport);
}
return $aMessageContent;
}
/**
* @param \DBObject $oObject
* @param string $sHeaderName {@see \ActionEmail::ENUM_HEADER_NAME_REFERENCES}, {@see \ActionEmail::ENUM_HEADER_NAME_MESSAGE_ID}
*
* @return string The formatted identifier for $sHeaderName based on $oObject
* @throws \Exception
* @since 3.1.0 N°4849
*/
protected function GenerateIdentifierForHeaders(DBObject $oObject, string $sHeaderName): string
{
$sObjClass = get_class($oObject);
$sObjId = $oObject->GetKey();
$sAppName = utils::Sanitize(ITOP_APPLICATION_SHORT, '', utils::ENUM_SANITIZATION_FILTER_VARIABLE_NAME);
switch ($sHeaderName) {
case static::ENUM_HEADER_NAME_MESSAGE_ID:
case static::ENUM_HEADER_NAME_REFERENCES:
// Prefix
$sPrefix = sprintf('%s_%s_%d', $sAppName, $sObjClass, $sObjId);
if ($sHeaderName === static::ENUM_HEADER_NAME_MESSAGE_ID) {
$sPrefix .= sprintf('_%F', microtime(true /* get as float*/));
}
// Suffix
$sSuffix = sprintf('@%s.openitop.org', MetaModel::GetEnvironmentId());
// Identifier
$sIdentifier = $sPrefix.$sSuffix;
if ($sHeaderName === static::ENUM_HEADER_NAME_REFERENCES) {
$sIdentifier = "<$sIdentifier>";
}
return $sIdentifier;
}
// Requested header name invalid
$sErrorMessage = sprintf('%s: Could not generate identifier for header "%s", only %s are supported', static::class, $sHeaderName, implode(' / ', [static::ENUM_HEADER_NAME_MESSAGE_ID, static::ENUM_HEADER_NAME_REFERENCES]));
IssueLog::Error($sErrorMessage, LogChannels::NOTIFICATIONS, [
'Object' => $sObjClass.'::'.$sObjId.' ('.$oObject->GetRawName().')',
'Action' => get_class($this).'::'.$this->GetKey().' ('.$this->GetRawName().')',
]);
throw new Exception($sErrorMessage);
}
/**
* Compose the body of the message from the 'body' attribute and the HTML template (if any)
* @since 3.1.0 N°4849
* @param bool $bHighlightPlaceholders If true add some extra HTML around placeholders to highlight them
* @return string
*/
protected function BuildMessageBody(bool $bHighlightPlaceholders = false): string
{
// Wrap content with a specific class in order to apply styles of HTML fields through the emogrifier (see `css/email.scss`)
$sBody = <<<HTML
<div class="email-is-html-content">
{$this->Get('body')}
</div>
HTML;
/** @var ormDocument $oHtmlTemplate */
$oHtmlTemplate = $this->Get('html_template');
if ($oHtmlTemplate && !$oHtmlTemplate->IsEmpty()) {
$sHtmlTemplate = $oHtmlTemplate->GetData();
if (false !== mb_strpos($sHtmlTemplate, static::TEMPLATE_BODY_CONTENT)) {
if ($bHighlightPlaceholders) {
// Highlight the $content$ block
$sBody = sprintf(static::CONTENT_HIGHLIGHT, $sBody);
}
$sBody = str_replace(static::TEMPLATE_BODY_CONTENT, $sBody, $oHtmlTemplate->GetData()); // str_replace is ok as long as both strings are well-formed UTF-8
} else {
$sBody = $oHtmlTemplate->GetData();
}
}
if($bHighlightPlaceholders) {
// Highlight all placeholders
$sBody = preg_replace('/\\$([^$]+)\\$/', static::FIELD_HIGHLIGHT, $sBody);
}
return $sBody;
}
/**
* @since 3.1.0 N°4849
* @inheritDoc
* @see cmdbAbstractObject::DisplayBareRelations()
*/
public function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, false);
if (!$bEditMode) {
$oPage->SetCurrentTab('action_email__preview', Dict::S('ActionEmail:preview_tab'), Dict::S('ActionEmail:preview_tab+'));
$sBody = $this->BuildMessageBody(true);
TwigHelper::RenderIntoPage($oPage, APPROOT.'/', 'templates/datamodel/ActionEmail/email-notification-preview', ['iframe_content' => $sBody]);
}
}
/**
* @since 3.1.0
* @inheritDoc
* @see cmdbAbstractObject::DoCheckToWrite()
*/
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
$oHtmlTemplate = $this->Get('html_template');
if ($oHtmlTemplate && !$oHtmlTemplate->IsEmpty()) {
if (false === mb_strpos($oHtmlTemplate->GetData(), static::TEMPLATE_BODY_CONTENT)) {
$this->m_aCheckWarnings[] = Dict::Format('ActionEmail:content_placeholder_missing', static::TEMPLATE_BODY_CONTENT, Dict::S('Class:ActionEmail/Attribute:body'));
}
}
}
/**
* @inheritDoc
* @since 3.2.0
*/
public static function GetAsynchronousGlobalSetting(): bool
{
return utils::GetConfig()->Get('email_asynchronous');
}
}

View File

@@ -0,0 +1,544 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// 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.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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\Service\Notification\Event\EventNotificationNewsroomService;
/**
* Persistent classes (internal): user defined actions
*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ExecAsyncTask implements iBackgroundProcess
{
public function GetPeriodicity()
{
return 2; // seconds
}
public function Process($iTimeLimit)
{
$sNow = date(AttributeDateTime::GetSQLFormat());
// Criteria: planned, and expected to occur... ASAP or in the past
$sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))";
$iProcessed = 0;
while (time() < $iTimeLimit)
{
// Next one ?
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
$oTask = $oSet->Fetch();
if (is_null($oTask))
{
// Nothing to be done
break;
}
$iProcessed++;
if ($oTask->Process())
{
$oTask->DBDelete();
}
}
return "processed $iProcessed tasks";
}
}
/**
* A
*
* @package iTopORM
*/
abstract class AsyncTask extends DBObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => array('created'),
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_async_task",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
);
MetaModel::Init_Params($aParams);
// Null is allowed to ease the migration from iTop 2.0.2 and earlier, when the status did not exist, and because the default value is not taken into account in the SQL definition
// The value is set from null to planned in the setup program
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('planned,running,idle,error'), "sql"=>"status", "default_value"=>"planned", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("created", array("allowed_values"=>null, "sql"=>"created", "default_value"=>"NOW()", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("started", array("allowed_values"=>null, "sql"=>"started", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("planned", array("allowed_values"=>null, "sql"=>"planned", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("event_id", array("targetclass"=>"Event", "jointype"=> "", "allowed_values"=>null, "sql"=>"event_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("remaining_retries", array("allowed_values"=>null, "sql"=>"remaining_retries", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("last_error_code", array("allowed_values"=>null, "sql"=>"last_error_code", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("last_error", array("allowed_values"=>null, "sql"=>"last_error", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("last_attempt", array("allowed_values"=>null, "sql"=>"last_attempt", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
}
/**
* Every is fine
*/
const OK = 0;
/**
* The task no longer exists
*/
const DELETED = 1;
/**
* The task is already being executed
*/
const ALREADY_RUNNING = 2;
/**
* The current process requests the ownership on the task.
* In case the task can be accessed concurrently, this function can be overloaded to add a critical section.
* The function must not block the caller if another process is already owning the task
*
* @return integer A code among OK/DELETED/ALREADY_RUNNING.
*/
public function MarkAsRunning()
{
try
{
if ($this->Get('status') == 'running')
{
return self::ALREADY_RUNNING;
}
else
{
$this->Set('status', 'running');
$this->Set('started', time());
$this->DBUpdate();
return self::OK;
}
}
catch(Exception $e)
{
// Corrupted task !! (for example: "Failed to reload object")
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - fatal error, deleting the task.');
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', 'Failed, corrupted data: '.$e->getMessage());
$oEventLog->DBUpdate();
}
$this->DBDelete();
return self::DELETED;
}
}
public function GetRetryDelay($iErrorCode = null)
{
$iRetryDelay = 600;
$aRetries = MetaModel::GetConfig()->Get('async_task_retries');
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
{
$aConfig = $aRetries[get_class($this)];
$iRetryDelay = $aConfig['retry_delay'] ?? $iRetryDelay;
}
return $iRetryDelay;
}
public function GetMaxRetries($iErrorCode = null)
{
$iMaxRetries = 0;
$aRetries = MetaModel::GetConfig()->Get('async_task_retries');
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
{
$aConfig = $aRetries[get_class($this)];
$iMaxRetries = $aConfig['max_retries'] ?? $iMaxRetries;
}
return $iMaxRetries;
}
public function IsRetryDelayExponential()
{
$bExponential = false;
$aRetries = MetaModel::GetConfig()->Get('async_task_retries');
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
{
$aConfig = $aRetries[get_class($this)];
$bExponential = (bool) ($aConfig['exponential_delay'] ?? $bExponential);
}
return $bExponential;
}
public static function CheckRetryConfig(Config $oConfig, $sAsyncTaskClass)
{
$aMessages = [];
$aRetries = $oConfig->Get('async_task_retries');
if (is_array($aRetries) && array_key_exists($sAsyncTaskClass, $aRetries))
{
$aValidKeys = array("retry_delay", "max_retries", "exponential_delay");
$aConfig = $aRetries[$sAsyncTaskClass];
if (!is_array($aConfig))
{
$aMessages[] = Dict::Format('Class:AsyncTask:InvalidConfig_Class_Keys', $sAsyncTaskClass, implode(', ', $aValidKeys));
}
else
{
foreach($aConfig as $key => $value)
{
if (!in_array($key, $aValidKeys))
{
$aMessages[] = Dict::Format('Class:AsyncTask:InvalidConfig_Class_InvalidKey_Keys', $sAsyncTaskClass, $key, implode(', ', $aValidKeys));
}
}
}
}
return $aMessages;
}
/**
* Compute the delay to wait for the "next retry", based on the given parameters
* @param bool $bIsExponential
* @param int $iRetryDelay
* @param int $iMaxRetries
* @param int $iRemainingRetries
* @return int
*/
public static function GetNextRetryDelay($bIsExponential, $iRetryDelay, $iMaxRetries, $iRemainingRetries)
{
if ($bIsExponential)
{
$iExponent = $iMaxRetries - $iRemainingRetries;
if ($iExponent < 0) $iExponent = 0; // Safety net in case on configuration change in the middle of retries
return $iRetryDelay * (2 ** $iExponent);
}
else
{
return $iRetryDelay;
}
}
/**
* Override to notify people that a task cannot be performed
*/
protected function OnDefinitiveFailure()
{
}
protected function OnInsert()
{
$this->Set('created', time());
}
/**
* @return boolean True if the task record can be deleted
*/
public function Process()
{
// By default: consider that the task is not completed
$bRet = false;
// Attempt to take the ownership
$iStatus = $this->MarkAsRunning();
if ($iStatus == self::OK)
{
try
{
$sStatus = $this->DoProcess();
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', $sStatus);
$oEventLog->DBUpdate();
}
$bRet = true;
} catch (Exception $e)
{
$this->HandleError($e->getMessage(), $e->getCode());
}
}
else
{
// Already done or being handled by another process... skip...
$bRet = false;
}
return $bRet;
}
/**
* Overridable to extend the behavior in case of error (logging)
*/
protected function HandleError($sErrorMessage, $iErrorCode)
{
if ($this->Get('last_attempt') == '')
{
// First attempt
$this->Set('remaining_retries', $this->GetMaxRetries($iErrorCode));
}
$this->SetTrim('last_error', $sErrorMessage);
$this->Set('last_error_code', $iErrorCode); // Note: can be ZERO !!!
$this->Set('last_attempt', time());
$iRemaining = $this->Get('remaining_retries');
if ($iRemaining > 0)
{
$iRetryDelay = $this->GetRetryDelay($iErrorCode);
$iNextRetryDelay = static::GetNextRetryDelay($this->IsRetryDelayExponential(), $iRetryDelay, $this->GetMaxRetries($iErrorCode), $iRemaining);
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage.' - remaining retries: '.$iRemaining.' - next retry in '.$iNextRetryDelay.'s');
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task. Remaining retries: $iRemaining. Next retry in {$iNextRetryDelay}s");
try
{
$oEventLog->DBUpdate();
}
catch (Exception $e)
{
$oEventLog->Set('message', "Failed to process async task. Remaining retries: $iRemaining. Next retry in {$iNextRetryDelay}s, more details in the log");
$oEventLog->DBUpdate();
}
}
$this->Set('remaining_retries', $iRemaining - 1);
$this->Set('status', 'planned');
$this->Set('started', null);
$this->Set('planned', time() + $iNextRetryDelay);
}
else
{
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage);
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task.");
try
{
$oEventLog->DBUpdate();
}
catch (Exception $e)
{
$oEventLog->Set('message', 'Failed to process async task, more details in the log');
$oEventLog->DBUpdate();
}
}
$this->Set('status', 'error');
$this->Set('started', null);
$this->Set('planned', null);
$this->OnDefinitiveFailure();
}
$this->DBUpdate();
}
/**
* Throws an exception (message and code)
*
* @return string
*/
abstract public function DoProcess();
/**
* Describes the error codes that DoProcess can return by the mean of exceptions
*/
static public function EnumErrorCodes()
{
return array();
}
}
/**
* An email notification
*
* @package iTopORM
*/
class AsyncSendEmail extends AsyncTask
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "created",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_async_send_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeInteger("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>Email::ORIGINAL_FORMAT, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
// MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance', 'trigger_list')); // Attributes to be displayed for the complete details
// MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
static public function AddToQueue(EMail $oEMail, $oLog)
{
$oNew = MetaModel::NewObject(__class__);
if ($oLog)
{
$oNew->Set('event_id', $oLog->GetKey());
}
$oNew->Set('to', $oEMail->GetRecipientTO(true /* string */));
$oNew->Set('subject', $oEMail->GetSubject());
$oNew->Set('version', 2);
$sMessage = $oEMail->SerializeV2();
$oNew->Set('message', $sMessage);
$oNew->DBInsert();
}
/**
* @inheritDoc
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function DoProcess()
{
$sMessage = $this->Get('message');
$iVersion = (int) $this->Get('version');
switch($iVersion)
{
case Email::FORMAT_V2:
$oEMail = Email::UnSerializeV2($sMessage);
break;
case Email::ORIGINAL_FORMAT:
$oEMail = unserialize($sMessage);
break;
default:
return 'Unknown version of the serialization format: '.$iVersion;
}
$iRes = $oEMail->Send($aIssues, true /* force synchro !!!!! */);
switch ($iRes)
{
case EMAIL_SEND_OK:
return "Sent";
case EMAIL_SEND_PENDING:
return "Bug - the email should be sent in synchronous mode";
case EMAIL_SEND_ERROR:
if (is_array($aIssues)) {
$sMessage = "Sending eMail failed: ".implode(', ', $aIssues);
} else {
$sMessage = "Sending eMail failed.";
}
throw new Exception($sMessage);
}
return '';
}
}
/**
* An async notification to be sent to iTop users through the newsroom
* @since 3.2.0
*/
class AsyncSendNewsroom extends AsyncTask {
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "created",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_async_send_newsroom",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeText("recipients", array("allowed_values"=>null, "sql"=>"recipients", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "allowed_values"=>null, "sql"=>"action_id", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "allowed_values"=>null, "sql"=>"trigger_id", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("title", array("allowed_values"=>null, "sql"=>"title", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("object_id", array("allowed_values"=>null, "sql"=>"object_id", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("object_class", array("allowed_values"=>null, "sql"=>"object_class", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("url", array("allowed_values"=>null, "sql"=>"url", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>'NOW()', "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
}
/**
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*/
public static function AddToQueue(int $iActionId, int $iTriggerId, array $aRecipients, string $sMessage, string $sTitle, string $sUrl, int $iObjectId, ?string $sObjectClass): void
{
$oNew = new static();
$oNew->Set('action_id', $iActionId);
$oNew->Set('trigger_id', $iTriggerId);
$oNew->Set('recipients', json_encode($aRecipients));
$oNew->Set('message', $sMessage);
$oNew->Set('title', $sTitle);
$oNew->Set('url', $sUrl);
$oNew->Set('object_id', $iObjectId);
$oNew->Set('object_class', $sObjectClass);
$oNew->SetCurrentDate('date');
$oNew->DBInsert();
}
/**
* @inheritDoc
*/
public function DoProcess()
{
$oAction = MetaModel::GetObject('Action', $this->Get('action_id'));
$iTriggerId = $this->Get('trigger_id');
$aRecipients = json_decode($this->Get('recipients'));
$sMessage = $this->Get('message');
$sTitle = $this->Get('title');
$sUrl = $this->Get('url');
$iObjectId = $this->Get('object_id');
$sObjectClass = $this->Get('object_class');
$sDate = $this->Get('date');
foreach ($aRecipients as $iRecipientId)
{
$oEvent = EventNotificationNewsroomService::MakeEventFromAction($oAction, $iRecipientId, $iTriggerId, $sMessage, $sTitle, $sUrl, $iObjectId, $sObjectClass, $sDate);
$oEvent->DBInsertNoReload();
}
return "Sent";
}
}

View File

@@ -4,96 +4,15 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory;
use Combodo\iTop\Application\UI\Links\Set\BlockLinkSetDisplayAsProperty;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Form\Field\LabelField;
use Combodo\iTop\Form\Field\TextAreaField;
use Combodo\iTop\Form\Form;
use Combodo\iTop\Form\Validator\CustomRegexpValidator;
use Combodo\iTop\Renderer\BlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Service\Links\LinkSetModel;
require_once('MyHelpers.class.inc.php');
require_once('ormdocument.class.inc.php');
require_once('ormstopwatch.class.inc.php');
require_once('ormpassword.class.inc.php');
require_once('ormcaselog.class.inc.php');
require_once('ormlinkset.class.inc.php');
require_once('ormset.class.inc.php');
require_once('ormtagset.class.inc.php');
require_once('htmlsanitizer.class.inc.php');
require_once('customfieldshandler.class.inc.php');
require_once('ormcustomfieldsvalue.class.inc.php');
require_once('datetimeformat.class.inc.php');
require_once(APPROOT.'/sources/Core/Orm/ormDocument.php');
require_once(APPROOT.'/sources/Core/Orm/ormStopWatch.php');
require_once(APPROOT.'/sources/Core/Orm/ormPassword.php');
require_once(APPROOT.'/sources/Core/Orm/ormCaseLog.php');
require_once(APPROOT.'/sources/Core/Orm/ormLinkSet.php');
require_once(APPROOT.'/sources/Core/Orm/ormSet.php');
require_once(APPROOT.'/sources/Core/Orm/ormTagSet.php');
require_once(APPROOT.'/sources/Core/Orm/ormCustomFieldsValue.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/MissingColumnException.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/iAttributeNoGroupBy.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDefinition.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDashboard.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeLinkedSet.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeLinkedSetIndirect.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDBFieldVoid.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDBField.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeInteger.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeObjectKey.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributePercentage.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDecimal.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeBoolean.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeString.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeClass.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeClassState.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeApplicationLanguage.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeFinalClass.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributePassword.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeEncryptedString.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeText.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeLongText.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeCaseLog.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeHTML.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeEmailAddress.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeIPAddress.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributePhoneNumber.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeOQL.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeTemplateString.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeTemplateText.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeTemplateHTML.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeEnum.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeMetaEnum.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDateTime.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDuration.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDate.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeDeadline.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeExternalKey.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeHierarchicalKey.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeExternalField.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeURL.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeBlob.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeImage.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeStopWatch.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeSubItem.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeOneWayPassword.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeTable.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributePropertySet.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeSet.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeEnumSet.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeClassAttCodeSet.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeQueryAttCodeSet.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeTagSet.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeFriendlyName.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeRedundancySettings.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeObsolescenceDate.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeCustomFields.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeArchiveFlag.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeArchiveDate.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeObsolescenceFlag.php');
require_once(APPROOT.'/sources/Core/AttributeDefinition/AttributeObsolescenceDate.php');
// Indexed array having two dimensions
// The PHP value is a hash array, it is stored as a TEXT column

View File

@@ -26,54 +26,17 @@ MetaModel::IncludeModule('application/query.class.inc.php');
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/Event.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/EventNotification.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/EventNotificationEmail.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/EventIssue.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/EventWebService.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/EventRestService.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/EventLoginUsage.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/Event/EventOnObject.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/AsyncTask/ExecAsyncTask.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/AsyncTask/AsyncTask.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/AsyncTask/AsyncSendEmail.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/AsyncTask/ExecAsyncTask.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/AsyncTask/AsyncSendNewsroom.php');
MetaModel::IncludeModule(APPROOT.'/core/email.class.inc.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/Action.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/ActionNotification.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/ActionEmail.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/lnkTriggerAction.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/Trigger.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnAttributeBlobDownload.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnObject.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnObjectCreate.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnObjectDelete.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnObjectMention.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnObjectUpdate.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnPortalUpdate.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnStateChange.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnStateEnter.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnStateLeave.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/DataModel/TriggerAndAction/TriggerOnThresholdReached.php');
//MetaModel::IncludeModule('core/bulkexport.class.inc.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/BulkExport/BulkExport.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/BulkExport/BulkExportException.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/BulkExport/BulkExportMissingParameterException.php');
MetaModel::IncludeModule(APPROOT.'/sources/Application/BulkExport/BulkExportResultGC.php');
MetaModel::IncludeModule('core/event.class.inc.php');
MetaModel::IncludeModule('core/action.class.inc.php');
MetaModel::IncludeModule('core/trigger.class.inc.php');
MetaModel::IncludeModule('core/bulkexport.class.inc.php');
MetaModel::IncludeModule('core/ownershiplock.class.inc.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/TagSetFieldData.php');
MetaModel::IncludeModule('core/tagsetfield.class.inc.php');
MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/BackgroundTask.php');
MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
MetaModel::IncludeModule('core/inlineimage.class.inc.php');
//MetaModel::IncludeModule('core/counter.class.inc.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/KeyValueStore.php');
MetaModel::IncludeModule(APPROOT.'/sources/Core/DataModel/TemporaryObjectDescriptor.php');
MetaModel::IncludeModule('core/counter.class.inc.php');
MetaModel::IncludeModule('core/TemporaryObjectDescriptor.php');
MetaModel::IncludeModule('webservices/webservices.basic.php');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,504 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
* This file is part of iTop.
*
* 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.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\WebPage\Page;
use Combodo\iTop\Application\WebPage\WebPage;
define('EXPORTER_DEFAULT_CHUNK_SIZE', 1000);
class BulkExportException extends Exception
{
protected $sLocalizedMessage;
public function __construct($message, $sLocalizedMessage, $code = 0, $previous = null)
{
parent::__construct($message, $code, $previous);
$this->sLocalizedMessage = $sLocalizedMessage;
}
public function GetLocalizedMessage()
{
return $this->sLocalizedMessage;
}
}
class BulkExportMissingParameterException extends BulkExportException
{
public function __construct($sFieldCode)
{
parent::__construct('Missing parameter: '.$sFieldCode, Dict::Format('Core:BulkExport:MissingParameter_Param', $sFieldCode));
}
}
/**
* Class BulkExport
*
* @copyright Copyright (C) 2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class BulkExportResult extends DBObject
{
public static function Init()
{
$aParams = array
(
"category" => 'core/cmdb',
"key_type" => 'autoincrement',
"name_attcode" => array('created'),
"state_attcode" => '',
"reconc_keys" => array(),
"db_table" => 'priv_bulk_export_result',
"db_key_field" => 'id',
"db_finalclass_field" => '',
"display_template" => '',
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeDateTime("created", array("allowed_values"=>null, "sql"=>"created", "default_value"=>"NOW()", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("user_id", array("allowed_values"=>null, "sql"=>"user_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("chunk_size", array("allowed_values"=>null, "sql"=>"chunk_size", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("format", array("allowed_values"=>null, "sql"=>"format", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("temp_file_path", array("allowed_values"=>null, "sql"=>"temp_file_path", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("search", array("allowed_values"=>null, "sql"=>"search", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("status_info", array("allowed_values"=>null, "sql"=>"status_info", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeBoolean("localize_output", array("allowed_values"=>null, "sql"=>"localize_output", "default_value"=>true, "is_null_allowed"=>true, "depends_on"=>array())));
}
/**
* @throws CoreUnexpectedValue
* @throws Exception
*/
public function ComputeValues()
{
$this->Set('user_id', UserRights::GetUserId());
}
}
/**
* Garbage collector for cleaning "old" export results from the database and the disk.
* This background process runs once per day and deletes the results of all exports which
* are older than one day.
*/
class BulkExportResultGC implements iBackgroundProcess
{
public function GetPeriodicity()
{
return 24*3600; // seconds
}
public function Process($iTimeLimit)
{
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time() - 24*3600); // Every BulkExportResult older than one day will be deleted
$sOQL = "SELECT BulkExportResult WHERE created < '$sDateLimit'";
$iProcessed = 0;
while (time() < $iTimeLimit)
{
// Next one ?
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
$oSet->OptimizeColumnLoad(array('BulkExportResult' => array('temp_file_path')));
$oResult = $oSet->Fetch();
if (is_null($oResult))
{
// Nothing to be done
break;
}
$iProcessed++;
@unlink($oResult->Get('temp_file_path'));
utils::PushArchiveMode(false);
$oResult->DBDelete();
utils::PopArchiveMode();
}
return "Cleaned $iProcessed old export results(s).";
}
}
/**
* Class BulkExport
*
* @copyright Copyright (C) 2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
abstract class BulkExport
{
protected $oSearch;
protected $iChunkSize;
protected $sFormatCode;
protected $aStatusInfo;
protected $oBulkExportResult;
protected $sTmpFile;
protected $bLocalizeOutput;
public function __construct()
{
$this->oSearch = null;
$this->iChunkSize = 0;
$this->sFormatCode = null;
$this->aStatusInfo = [
'show_obsolete_data' => utils::ShowObsoleteData(),
];
$this->oBulkExportResult = null;
$this->sTmpFile = '';
$this->bLocalizeOutput = false;
}
/**
* Find the first class capable of exporting the data in the given format
*
* @param string $sFormatCode The lowercase format (e.g. html, csv, spreadsheet, xlsx, xml, json, pdf...)
* @param DBSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats
*
* @return BulkExport|null
* @throws ReflectionException
*/
static public function FindExporter($sFormatCode, $oSearch = null)
{
foreach(get_declared_classes() as $sPHPClass)
{
$oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->isSubclassOf('BulkExport') && !$oRefClass->isAbstract())
{
/** @var BulkExport $oBulkExporter */
$oBulkExporter = new $sPHPClass();
if ($oBulkExporter->IsFormatSupported($sFormatCode, $oSearch))
{
if ($oSearch)
{
$oBulkExporter->SetObjectList($oSearch);
}
return $oBulkExporter;
}
}
}
return null;
}
/**
* Find the exporter corresponding to the given persistent token
*
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
*
* @return BulkExport|null
* @throws ArchivedObjectException
* @throws CoreException
* @throws ReflectionException
*/
static public function FindExporterFromToken($iPersistentToken = null)
{
$oBulkExporter = null;
$oInfo = MetaModel::GetObject('BulkExportResult', $iPersistentToken, false);
if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId()))
{
$sFormatCode = $oInfo->Get('format');
$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($aStatusInfo);
$oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output'));
$oBulkExporter->sTmpFile = $oInfo->Get('temp_file_path');
$oBulkExporter->oBulkExportResult = $oInfo;
}
}
return $oBulkExporter;
}
/**
* @param $data
* @throws Exception
*/
public function AppendToTmpFile($data)
{
if ($this->sTmpFile == '')
{
$this->sTmpFile = $this->MakeTmpFile($this->GetFileExtension());
}
$hFile = fopen($this->sTmpFile, 'ab');
if ($hFile !== false)
{
fwrite($hFile, $data);
fclose($hFile);
}
}
public function GetTmpFilePath()
{
return $this->sTmpFile;
}
/**
* Lists all possible export formats. The output is a hash array in the form: 'format_code' => 'localized format label'
* @return array :string
*/
static public function FindSupportedFormats()
{
$aSupportedFormats = array();
foreach(get_declared_classes() as $sPHPClass)
{
$oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->isSubClassOf('BulkExport') && !$oRefClass->isAbstract())
{
$oBulkExporter = new $sPHPClass;
$aFormats = $oBulkExporter->GetSupportedFormats();
$aSupportedFormats = array_merge($aSupportedFormats, $aFormats);
}
}
return $aSupportedFormats;
}
/**
* (non-PHPdoc)
* @see iBulkExport::SetChunkSize()
*/
public function SetChunkSize($iChunkSize)
{
$this->iChunkSize = $iChunkSize;
}
/**
* @param $bLocalizeOutput
*/
public function SetLocalizeOutput($bLocalizeOutput)
{
$this->bLocalizeOutput = $bLocalizeOutput;
}
/**
* (non-PHPdoc)
* @see iBulkExport::SetObjectList()
*/
public function SetObjectList(DBSearch $oSearch)
{
$oSearch->SetShowObsoleteData($this->aStatusInfo['show_obsolete_data']);
$this->oSearch = $oSearch;
}
public function SetFormat($sFormatCode)
{
$this->sFormatCode = $sFormatCode;
}
/**
* (non-PHPdoc)
* @see iBulkExport::IsFormatSupported()
*/
public function IsFormatSupported($sFormatCode, $oSearch = null)
{
return array_key_exists($sFormatCode, $this->GetSupportedFormats());
}
/**
* (non-PHPdoc)
* @see iBulkExport::GetSupportedFormats()
*/
public function GetSupportedFormats()
{
return array(); // return array('csv' => Dict::S('UI:ExportFormatCSV'));
}
public function SetHttpHeaders(WebPage $oPage)
{
}
/**
* @return string
*/
public function GetHeader()
{
return '';
}
abstract public function GetNextChunk(&$aStatus);
/**
* @return string
*/
public function GetFooter()
{
return '';
}
public function SaveState()
{
if ($this->oBulkExportResult === null)
{
$this->oBulkExportResult = new BulkExportResult();
$this->oBulkExportResult->Set('format', $this->sFormatCode);
$this->oBulkExportResult->Set('search', $this->oSearch->serialize());
$this->oBulkExportResult->Set('chunk_size', $this->iChunkSize);
$this->oBulkExportResult->Set('localize_output', $this->bLocalizeOutput);
}
$this->oBulkExportResult->Set('status_info', json_encode($this->GetStatusInfo()));
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
utils::PushArchiveMode(false);
$ret = $this->oBulkExportResult->DBWrite();
utils::PopArchiveMode();
return $ret;
}
public function Cleanup()
{
if (($this->oBulkExportResult && (!$this->oBulkExportResult->IsNew())))
{
$sFilename = $this->oBulkExportResult->Get('temp_file_path');
if ($sFilename != '')
{
@unlink($sFilename);
}
utils::PushArchiveMode(false);
$this->oBulkExportResult->DBDelete();
utils::PopArchiveMode();
}
}
public function EnumFormParts()
{
return array();
}
/**
* @param WebPage $oP
* @param $sPartId
*
* @return UIContentBlock
*/
public function GetFormPart(WebPage $oP, $sPartId)
{
}
public function DisplayUsage(Page $oP)
{
}
public function ReadParameters()
{
$this->bLocalizeOutput = !((bool)utils::ReadParam('no_localize', 0, true, 'integer'));
}
public function GetResultAsHtml()
{
}
public function GetRawResult()
{
}
/**
* @return string
*/
public function GetMimeType()
{
return '';
}
/**
* @return string
*/
public function GetFileExtension()
{
return '';
}
public function GetCharacterSet()
{
return 'UTF-8';
}
public function GetStatistics()
{
}
public function SetFields($sFields)
{
}
public function GetDownloadFileName()
{
return Dict::Format('Core:BulkExportOf_Class', MetaModel::GetName($this->oSearch->GetClass())).'.'.$this->GetFileExtension();
}
public function SetStatusInfo($aStatusInfo)
{
$this->aStatusInfo = $aStatusInfo;
}
public function GetStatusInfo()
{
return $this->aStatusInfo;
}
/**
* @param $sExtension
* @return string
* @throws Exception
*/
protected function MakeTmpFile($sExtension)
{
if(!is_dir(utils::GetDataPath()."bulk_export"))
{
@mkdir(utils::GetDataPath()."bulk_export", 0777, true /* recursive */);
clearstatcache();
}
if (!is_writable(utils::GetDataPath()."bulk_export"))
{
throw new Exception('Data directory "'.utils::GetDataPath().'bulk_export" could not be written.');
}
$iNum = rand();
do
{
$iNum++;
$sToken = sprintf("%08x", $iNum);
$sFileName = utils::GetDataPath()."bulk_export/$sToken.".$sExtension;
$hFile = @fopen($sFileName, 'x');
}
while($hFile === false);
fclose($hFile);
return $sFileName;
}
}
// The built-in exports
require_once(APPROOT.'core/tabularbulkexport.class.inc.php');
require_once(APPROOT.'core/htmlbulkexport.class.inc.php');
if (extension_loaded('gd'))
{
// PDF export - via TCPDF - requires GD
require_once(APPROOT.'core/pdfbulkexport.class.inc.php');
}
require_once(APPROOT.'core/csvbulkexport.class.inc.php');
require_once(APPROOT.'core/excelbulkexport.class.inc.php');
require_once(APPROOT.'core/spreadsheetbulkexport.class.inc.php');
require_once(APPROOT.'core/xmlbulkexport.class.inc.php');

File diff suppressed because it is too large Load Diff

View File

@@ -40,15 +40,9 @@ require_once('dict.class.inc.php');
require_once('attributedef.class.inc.php');
require_once('stimulus.class.inc.php');
require_once('valuesetdef.class.inc.php');
require_once('MyHelpers.class.inc.php');
require_once(APPROOT.'/sources/Core/ValueSetDefinition/ValueSetDefinition.php');
require_once(APPROOT.'/sources/Core/ValueSetDefinition/ValueSetObjects.php');
require_once(APPROOT.'/sources/Core/ValueSetDefinition/ValueSetEnum.php');
require_once(APPROOT.'/sources/Core/ValueSetDefinition/ValueSetEnumPadded.php');
require_once(APPROOT.'/sources/Core/ValueSetDefinition/ValueSetRange.php');
require_once(APPROOT.'/sources/Core/ValueSetDefinition/ValueSetEnumClasses.php');
require_once('oql/expression.class.inc.php');
require_once('oql/oqlquery.class.inc.php');
require_once('oql/oqlexception.class.inc.php');
@@ -65,41 +59,12 @@ require_once('dbobject.class.php');
require_once('dbobjectset.class.php');
require_once('backgroundprocess.inc.php');
//require_once('asynctask.class.inc.php');
require_once(APPROOT.'/sources/Core/DataModel/AsyncTask/ExecAsyncTask.php');
require_once(APPROOT.'/sources/Core/DataModel/AsyncTask/AsyncTask.php');
require_once(APPROOT.'/sources/Core/DataModel/AsyncTask/AsyncSendEmail.php');
require_once(APPROOT.'/sources/Core/DataModel/AsyncTask/ExecAsyncTask.php');
require_once(APPROOT.'/sources/Core/DataModel/AsyncTask/AsyncSendNewsroom.php');
require_once(APPROOT.'/sources/Core/DataModel/DBProperty.php');
require_once('asynctask.class.inc.php');
require_once('dbproperty.class.inc.php');
// db change tracking data model
require_once('cmdbchange.class.inc.php');
//require_once('cmdbchangeop.class.inc.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/iCMDBChangeOp.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOp.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpCreate.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpDelete.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpPlugin.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttribute.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeBlob.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeCaseLog.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeCustomFields.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeEncrypted.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeHTML.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeLinks.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeLinksAddRemove.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeLinksTune.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeLongText.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeOneWayPassword.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeScalar.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeTagSet.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeText.php');
require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttributeURL.php');
require_once('cmdbchangeop.class.inc.php');
// customization data model
// Romain: temporary moved into application.inc.php (see explanations there)
@@ -112,7 +77,7 @@ require_once(APPROOT.'/sources/Core/DataModel/CMDBChange/CMDBChangeOpSetAttribut
require_once('templatestring.class.inc.php');
require_once('csvparser.class.inc.php');
//require_once('bulkchange.class.inc.php');
require_once('bulkchange.class.inc.php');
/**
* A persistent object, which changes are accurately recorded

View File

@@ -42,11 +42,6 @@ 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)
@@ -220,7 +215,7 @@ class CMDBSource
* @param string $sServer server 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
* @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.
*
* @link http://php.net/manual/en/mysqli.persistconns.php documentation for the "p:" prefix (persistent connexion)
*/
@@ -363,15 +358,6 @@ class CMDBSource
return static::QueryToScalar('SELECT VERSION()', 0);
}
/**
* @deprecated Use `CMDBSource::GetDBVersion` instead.
* @uses mysqli_get_server_info
*/
public static function GetServerInfo()
{
return mysqli_get_server_info(DbConnectionWrapper::GetDbConnection());
}
/**
* Get the DB vendor between MySQL and its main forks
* @return string

View File

@@ -20,6 +20,9 @@
*/
use Combodo\iTop\Config\Validator\iTopConfigAstValidator;
use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator;
define('ITOP_APPLICATION', 'iTop');
define('ITOP_APPLICATION_SHORT', 'iTop');
@@ -49,13 +52,7 @@ define('ACCESS_READONLY', 0);
*/
require_once('attributedef.class.inc.php'); // For the defines
require_once(APPROOT.'/sources/Application/SimpleCrypt/CryptEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCrypt.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptMcryptEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptOpenSSLEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptOpenSSLMcryptCompatibilityEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptSimpleEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptSodiumEngine.php');
require_once('simplecrypt.class.inc.php');
// was utf8 but it only supports BMP chars (https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)
// so we switched to utf8mb4 in iTop 2.5, adding dependency to MySQL 5.5.3
@@ -1800,14 +1797,6 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'application.secret' => [
'type' => 'string',
'description' => 'Application secret, uses this value for encrypting the cookies used in the remember me functionality and for creating signed URIs when using ESI (Edge Side Includes).',
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'http.request.user_agent' => [
'type' => 'string',
'description' => 'HTTP request user agent, use this to set a custom agent on external requests.',
@@ -1822,6 +1811,7 @@ class Config
return (array_key_exists($sPropCode, $this->m_aSettings));
}
/**
* @return string identifier that can be used for example to name WebStorage/SessionStorage keys (they
* are related to a whole domain, and a domain can host multiple itop)
@@ -2200,7 +2190,6 @@ class Config
$this->m_sEncryptionLibrary = isset($MySettings['encryption_library']) ? trim($MySettings['encryption_library']) : $this->m_sEncryptionLibrary;
$this->m_aCharsets = isset($MySettings['csv_import_charsets']) ? $MySettings['csv_import_charsets'] : array();
$this->m_iPasswordHashAlgo = isset($MySettings['password_hash_algo']) ? $MySettings['password_hash_algo'] : $this->m_iPasswordHashAlgo;
$this->m_sAppSecret = isset($MySettings['application.secret']) ? trim($MySettings['application.secret']) : $this->m_sAppSecret;
}
protected function Verify()
@@ -2261,6 +2250,9 @@ class Config
$this->m_aModuleSettings[$sModule][$sProperty] = $value;
}
/**
* @deprecated 3.3.0 N°8190
*/
public function GetAddons()
{
if (array_key_exists("user rights", $this->m_aAddons)) {
@@ -2270,6 +2262,9 @@ class Config
}
}
/**
* @deprecated 3.3.0 N°8190
*/
public function SetAddons($aAddons)
{
$this->m_aAddons = $aAddons;
@@ -2518,7 +2513,6 @@ class Config
$aSettings['encryption_library'] = $this->m_sEncryptionLibrary;
$aSettings['csv_import_charsets'] = $this->m_aCharsets;
$aSettings['password_hash_algo'] = $this->m_iPasswordHashAlgo;
$aSettings['application.secret'] = $this->m_sAppSecret;
foreach ($this->m_aModuleSettings as $sModule => $aProperties)
{
@@ -2632,7 +2626,6 @@ class Config
'encryption_library' => $this->m_sEncryptionLibrary,
'csv_import_charsets' => $this->m_aCharsets,
'password_hash_algo' => $this->m_iPasswordHashAlgo,
'application.secret' => $this->m_sAppSecret,
);
foreach ($aOtherValues as $sKey => $value)
{
@@ -2848,20 +2841,8 @@ class Config
}
}
}
if (isset($aModuleInfo['installer']))
{
$sModuleInstallerClass = $aModuleInfo['installer'];
if (!class_exists($sModuleInstallerClass))
{
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']);
}
if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI'))
{
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']);
}
$aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig');
call_user_func_array($aCallSpec, array($this));
}
RunTimeEnvironment::CallInstallerHandler($aModuleInfo, "BeforeWritingConfig", [$this]);
}
}
$this->SetAddOns($aAddOns);
@@ -3029,4 +3010,6 @@ class ConfigPlaceholdersResolver
IssueLog::Error($sErrorMessage, self::class, array($sSourceName, $sKey, $sDefault, $sWholeMask));
throw new ConfigException($sErrorMessage);
}
}

251
core/counter.class.inc.php Normal file
View File

@@ -0,0 +1,251 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
* This file is part of iTop.
*
* 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.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
/**
* Class ItopCounter
*
*/
final class ItopCounter
{
/**
* Key based counter.
* The counter is protected against concurrency script.
*
* @param $sCounterName
* @param null|callable $oNewObjectValueProvider optional callable that must return an integer. Used when no key is found
*
* @return int the counter starting at
* * `0` when no $oNewObjectValueProvider is given (or null)
* * `$oNewObjectValueProvider() + 1` otherwise
*
* @throws \CoreException
* @throws \MySQLException
* @throws \Exception
*/
public static function Inc($sCounterName, $oNewObjectValueProvider = null)
{
$sSelfClassName = self::class;
$sMutexKeyName = "{$sSelfClassName}-{$sCounterName}";
$oiTopMutex = new iTopMutex($sMutexKeyName);
$oiTopMutex->Lock();
$bIsInsideTransaction = CMDBSource::IsInsideTransaction();
if ($bIsInsideTransaction)
{
// # Transaction isolation hack:
// When inside a transaction, we need to open a new connection for the counter.
// So it is visible immediately to the connections outside of the transaction.
// Either way, the lock is not long enought, and there would be duplicate ref.
//
// SELECT ... FOR UPDATE would have also worked but with the cost of extra long lock (until the commit),
// we did not wanted this! As opening a short connection is less prone to starving than a long running one.
// Plus it would trigger way more deadlocks!
$hDBLink = self::InitMySQLSession();
}
else
{
$hDBLink = CMDBSource::GetMysqli();
}
try
{
$oFilter = DBObjectSearch::FromOQL('SELECT KeyValueStore WHERE key_name=:key_name AND namespace=:namespace', array(
'key_name' => $sCounterName,
'namespace' => $sSelfClassName,
));
$oAttDef = MetaModel::GetAttributeDef(KeyValueStore::class, 'value');
$aAttToLoad = array(KeyValueStore::class => array('value' => $oAttDef));
$sSql = $oFilter->MakeSelectQuery(array(), array(), $aAttToLoad);
$hResult = mysqli_query($hDBLink, $sSql);
$aCounter = mysqli_fetch_array($hResult, MYSQLI_NUM);
mysqli_free_result($hResult);
//Rebuild the filter, as the MakeSelectQuery polluted the orignal and it cannot be reused
$oFilter = DBObjectSearch::FromOQL('SELECT KeyValueStore WHERE key_name=:key_name AND namespace=:namespace', array(
'key_name' => $sCounterName,
'namespace' => $sSelfClassName,
));
if (is_null($aCounter))
{
if (null != $oNewObjectValueProvider)
{
$iComputedValue = $oNewObjectValueProvider();
}
else
{
$iComputedValue = 0;
}
$iCurrentValue = $iComputedValue + 1;
$aQueryParams = array(
'key_name' => $sCounterName,
'value' => "$iCurrentValue",
'namespace' => $sSelfClassName,
);
$sSql = $oFilter->MakeInsertQuery($aQueryParams);
}
else
{
$iCurrentValue = (int) $aCounter[1];
$iCurrentValue++;
$aQueryParams = array(
'value' => "$iCurrentValue",
);
$sSql = $oFilter->MakeUpdateQuery($aQueryParams);
}
$hResult = mysqli_query($hDBLink, $sSql);
}
catch(Exception $e)
{
IssueLog::Error($e->getMessage());
throw $e;
}
finally
{
if ($bIsInsideTransaction)
{
mysqli_close($hDBLink);
}
$oiTopMutex->Unlock();
}
return $iCurrentValue;
}
/**
* handle a counter for the root class of given $sLeafClass.
* If no counter exist initialize it with the `max(id) + 1`
*
*
*
* @param $sLeafClass
*
* @return int
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreOqlMultipleResultsForbiddenException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*/
public static function IncClass($sLeafClass)
{
$sRootClass = MetaModel::GetRootClass($sLeafClass);
$oNewObjectCallback = function() use ($sRootClass)
{
$sRootTable = MetaModel::DBGetTable($sRootClass);
$sIdField = MetaModel::DBGetKey($sRootClass);
return CMDBSource::QueryToScalar("SELECT max(`$sIdField`) FROM `$sRootTable`");
};
return self::Inc($sRootClass, $oNewObjectCallback);
}
/**
* @return \mysqli
* @throws \ConfigException
* @throws \CoreException
* @throws \MySQLException
*/
private static function InitMySQLSession()
{
$oConfig = utils::GetConfig();
$sDBHost = $oConfig->Get('db_host');
$sDBUser = $oConfig->Get('db_user');
$sDBPwd = $oConfig->Get('db_pwd');
$sDBName = $oConfig->Get('db_name');
$bDBTlsEnabled = $oConfig->Get('db_tls.enabled');
$sDBTlsCA = $oConfig->Get('db_tls.ca');
$hDBLink = CMDBSource::GetMysqliInstance($sDBHost, $sDBUser, $sDBPwd, $sDBName, $bDBTlsEnabled, $sDBTlsCA, false);
if (!$hDBLink)
{
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;
}
}
/**
* Persistent classes for a CMDB
*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class KeyValueStore extends DBObject
{
public static function Init()
{
$aParams = array(
'category' => '',
'key_type' => 'autoincrement',
'name_attcode' => array('key_name'),
'state_attcode' => '',
'reconc_keys' => array(''),
'db_table' => 'key_value_store',
'db_key_field' => 'id',
'db_finalclass_field' => '',
'indexes' => array (
array (
0 => 'key_name',
1 => 'namespace',
),
),);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("namespace", array("allowed_values"=>null, "sql"=>'namespace', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeString("key_name", array("allowed_values"=>null, "sql"=>'key_name', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>'value', "default_value"=>'0', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_SetZListItems('details', array (
0 => 'key_name',
1 => 'value',
2 => 'namespace',
));
MetaModel::Init_SetZListItems('standard_search', array (
0 => 'key_name',
1 => 'value',
2 => 'namespace',
));
MetaModel::Init_SetZListItems('list', array (
0 => 'key_name',
1 => 'value',
2 => 'namespace',
));
;
}
}

View File

@@ -62,7 +62,7 @@ require_once('mutex.class.inc.php');
/**
* A persistent object, as defined by the metamodel
* A persistent object, as defined by the metamodel
*
* @package iTopORM
* @api
@@ -314,9 +314,9 @@ abstract class DBObject implements iDisplay
/**
* Whether the object is already persisted in DB or not.
*
*
* @api
*
*
* @return bool
*/
public function IsNew()
@@ -326,9 +326,9 @@ abstract class DBObject implements iDisplay
/**
* Returns an Id for memory objects
*
*
* @internal
*
*
* @param string $sClass
*
* @return int
@@ -365,7 +365,7 @@ abstract class DBObject implements iDisplay
$sRet .= "<b title=\"$sRootClass\">$sClass</b>::$iPKey ($sFriendlyname)<br/>\n";
return $sRet;
}
/**
* Alias of DBObject::Reload()
*
@@ -388,7 +388,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see m_bFullyLoaded
*
*
* @return bool
* @throws CoreException
*/
@@ -411,11 +411,9 @@ abstract class DBObject implements iDisplay
* This is mostly used after a lazy load (automatically performed by the framework)
* This will erase any pending changes.
*
* @param bool $bAllowAllData @deprecated This parameter is ignored!!
*
* @throws CoreException
*/
public function Reload($bAllowAllData = false)
public function Reload()
{
assert($this->m_bIsInDB);
$this->FireEvent(EVENT_DB_OBJECT_RELOAD);
@@ -511,7 +509,7 @@ abstract class DBObject implements iDisplay
{
$aAttList = $aAttToLoad[$sClassAlias];
}
foreach($aAttList as $sAttCode=>$oAttDef)
{
// Skip links (could not be loaded by the mean of this query)
@@ -571,7 +569,7 @@ abstract class DBObject implements iDisplay
$bFullyLoaded = false;
}
}
// Load extended data
if ($aExtendedDataSpec != null)
{
@@ -595,7 +593,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see Set()
*
*
* @param string $sAttCode
* @param mixed $value
*/
@@ -850,11 +848,11 @@ abstract class DBObject implements iDisplay
/**
* Get the label of an attribute.
*
*
* Shortcut to the field's AttributeDefinition->GetLabel()
*
* @api
*
*
* @param string $sAttCode
*
* @return string
@@ -927,7 +925,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see Get
*
*
* @param string $sAttCode
*
* @return int|mixed|null
@@ -1032,7 +1030,7 @@ abstract class DBObject implements iDisplay
* Returns the default value of the $sAttCode.
*
* Returns the default value of the given attribute.
*
*
* @internal
*
* @param string $sAttCode
@@ -1053,12 +1051,12 @@ abstract class DBObject implements iDisplay
* @internal
*
* @return array|null
*/
*/
public function GetExtendedData()
{
return $this->m_aExtendedData;
}
/**
* Set the HighlightCode
*
@@ -1080,7 +1078,7 @@ abstract class DBObject implements iDisplay
{
$fCurrentRank = $aHighlightScale[$this->m_sHighlightCode]['rank'];
}
if (array_key_exists($sCode, $aHighlightScale))
{
$fRank = $aHighlightScale[$sCode]['rank'];
@@ -1090,13 +1088,13 @@ abstract class DBObject implements iDisplay
}
}
}
/**
* Get the current HighlightCode
*
*
* @internal
* @used-by DBObject::ComputeHighlightCode()
*
*
* @return string|null The Hightlight code (null if none set, meaning rank = 0)
*/
protected function GetHighlightCode()
@@ -1145,7 +1143,7 @@ abstract class DBObject implements iDisplay
* corresponding to the external key and getting the value from it
*
* UNUSED ?
*
*
* @internal
* @todo: check if this is dead code.
*
@@ -1216,7 +1214,7 @@ abstract class DBObject implements iDisplay
/**
* @api
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1262,11 +1260,11 @@ abstract class DBObject implements iDisplay
/**
* Get the value as it must be in the edit areas (forms)
*
*
* Makes a raw text representation of the value.
*
* @internal
*
*
* @param string $sAttCode
*
* @return int|mixed|string
@@ -1296,7 +1294,7 @@ abstract class DBObject implements iDisplay
else
{
$sEditValue = 0;
}
}
}
else
{
@@ -1312,14 +1310,14 @@ abstract class DBObject implements iDisplay
/**
* Get $sAttCode formatted as XML
*
*
* The returned value is a text that is suitable for insertion into an XML node.
* Depending on the type of attribute, the returned text is either:
* * A literal, with XML entities already escaped,
* * XML
*
* @api
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1357,10 +1355,10 @@ abstract class DBObject implements iDisplay
}
/**
*
*
* @see GetAsHTML()
* @see GetOriginal()
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1535,7 +1533,7 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
*
* @param string $sClass
*
* @return mixed
@@ -1590,7 +1588,7 @@ abstract class DBObject implements iDisplay
* Get the id
*
* @api
*
*
* @return string|null
*/
public function GetKey()
@@ -1601,7 +1599,7 @@ abstract class DBObject implements iDisplay
/**
* Primary key Setter
* Usable only for not yet persisted DBObjects
*
*
* @internal
*
* @param int $iNewKey the desired identifier
@@ -1614,7 +1612,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("An object id must be an integer value ($iNewKey)");
}
if ($this->m_bIsInDB && !empty($this->m_iKey) && ($this->m_iKey != $iNewKey))
{
throw new CoreException("Changing the key ({$this->m_iKey} to $iNewKey) on an object (class {".get_class($this).") wich already exists in the Database");
@@ -1624,7 +1622,7 @@ abstract class DBObject implements iDisplay
/**
* Get the icon representing this object
*
*
* @api
*
* @param boolean $bImgTag If true the result is a full IMG tag (or an empty string if no icon is defined)
@@ -1714,7 +1712,7 @@ abstract class DBObject implements iDisplay
*
* Returns the label as defined in the dictionary for the language of the current user
*
* @api
* @api
*
* @return string (empty for default name scheme)
*/
@@ -1781,7 +1779,7 @@ abstract class DBObject implements iDisplay
/**
* Helper to get the state
*
*
* @api
*
* @return mixed|string '' if no state attribute, object representing its value otherwise
@@ -1803,9 +1801,9 @@ abstract class DBObject implements iDisplay
/**
* Get the label (raw text) of the current state
* helper for MetaModel::GetStateLabel()
*
*
* @api
*
*
* @return mixed|string
*
* @throws ArchivedObjectException
@@ -1852,7 +1850,7 @@ abstract class DBObject implements iDisplay
* Define attributes read-only from the end-user perspective
*
* @return array|null List of attcodes
*/
*/
public static function GetReadOnlyAttributes()
{
return null;
@@ -1861,14 +1859,14 @@ abstract class DBObject implements iDisplay
/**
* Get predefined objects
*
*
* The predefined objects will be synchronized with the DB at each install/upgrade
* As soon as a class has predefined objects, then nobody can create nor delete objects
*
* @internal
*
* @return array An array of id => array of attcode => php value(so-called "real value": integer, string, ormDocument, DBObjectSet, etc.)
*/
*/
public static function GetPredefinedObjects()
{
return null;
@@ -1997,7 +1995,7 @@ abstract class DBObject implements iDisplay
* Note: Attributes (and flags) from the target state and the transition are combined.
*
* @internal
*
*
* @param string $sStimulus
* @param string $sOriginState Default is current state
*
@@ -2197,7 +2195,7 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
*
* @throws \CoreException
* @throws \OQLException
*
@@ -4929,7 +4927,7 @@ abstract class DBObject implements iDisplay
{
if (is_null($sWorkingTimeComputer))
{
$sWorkingTimeComputer = class_exists('SLAComputation') ? 'SLAComputation' : 'DefaultWorkingTimeComputer';
$sWorkingTimeComputer = MetaModel::GetWorkingTime(get_class($this));
}
$oComputer = new $sWorkingTimeComputer();
$aCallSpec = array($oComputer, 'GetOpenDuration');
@@ -5323,7 +5321,7 @@ abstract class DBObject implements iDisplay
// Keep track of link changes
//
if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_DETAILS) == 0) continue;
$iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
$oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksTune');
if ($oMyChangeOp)
@@ -5520,27 +5518,6 @@ abstract class DBObject implements iDisplay
set_time_limit(intval($iPreviousTimeLimit));
}
/**
* Caching relying on an object set is not efficient since 2.0.3
* Use GetSynchroData instead
*
* Get all the synchro replica related to this object
*
* @internal
* @deprecated
*
* @return DBObjectSet Set with two columns: R=SynchroReplica S=SynchroDataSource
* @throws \OQLException
*/
public function GetMasterReplica()
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
$sOQL = "SELECT replica,datasource FROM SynchroReplica AS replica JOIN SynchroDataSource AS datasource ON replica.sync_source_id=datasource.id WHERE replica.dest_class = :dest_class AND replica.dest_id = :dest_id";
$oReplicaSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('dest_class' => get_class($this), 'dest_id' => $this->GetKey()));
return $oReplicaSet;
}
/**
* Get all the synchro data related to this object
*

View File

@@ -236,78 +236,6 @@ abstract class DBSearch
*/
abstract public function IsAny();
/**
* @internal
* @deprecated use ToOQL() instead
* @return string
*/
public function Describe()
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
return 'deprecated - use ToOQL() instead';
}
/**
* @internal
* @deprecated use ToOQL() instead
* @return string
*/
public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
return 'deprecated - use ToOQL() instead';
}
/**
* @internal
* @deprecated use ToOQL() instead
* @return string
*/
public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
return 'deprecated - use ToOQL() instead';
}
/**
* @internal
* @deprecated use ToOQL() instead
* @return string
*/
public function DescribeConditionRelTo($aRelInfo)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
return 'deprecated - use ToOQL() instead';
}
/**
* @internal
* @deprecated use ToOQL() instead
* @return string
*/
public function DescribeConditions()
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
return 'deprecated - use ToOQL() instead';
}
/**
* @internal
* @deprecated use ToOQL() instead
* @return string
*/
public function __DescribeHTML()
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
return 'deprecated - use ToOQL() instead';
}
/**
* @internal
* @return mixed
@@ -1716,16 +1644,6 @@ abstract class DBSearch
return $this->ToOQL(true);
}
/**
* @return array{\VariableExpression}
*
* @deprecated use DBSearch::GetExpectedArguments() instead
*/
public function ListParameters(): array
{
return $this->GetExpectedArguments();
}
/**
* Get parameters from the condition expression(s)
*

View File

@@ -114,9 +114,6 @@ class EMail implements iEMail
* @param string $sSerializedMessage The serialized representation of the message
*
* @return \Email
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException
*/
public static function UnSerializeV2($sSerializedMessage)
{

426
core/event.class.inc.php Normal file
View File

@@ -0,0 +1,426 @@
<?php
use Combodo\iTop\Application\WebPage\WebPage;
/**
* Copyright (C) 2013-2024 Combodo SAS
*
* This file is part of iTop.
*
* 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.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
class Event extends DBObject implements iDisplay
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"NOW()", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'message', 'userinfo')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'finalclass', 'message')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
/**
* Maps the given context parameter name to the appropriate filter/search code for this class
* @param string $sContextParam Name of the context parameter, i.e. 'org_id'
* @return string Filter code, i.e. 'customer_id'
*/
public static function MapContextParam($sContextParam)
{
if ($sContextParam == 'menu')
{
return null;
}
else
{
return $sContextParam;
}
}
/**
* This function returns a 'hilight' CSS class, used to hilight a given row in a table
* There are currently (i.e defined in the CSS) 4 possible values HILIGHT_CLASS_CRITICAL,
* HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
* To Be overridden by derived classes
* @param void
* @return String The desired higlight class for the object/row
*/
public function GetHilightClass()
{
// Possible return values are:
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
return HILIGHT_CLASS_NONE; // Not hilighted by default
}
public static function GetUIPage()
{
return 'UI.php';
}
function DisplayDetails(WebPage $oPage, $bEditMode = false)
{
// Object's details
//$this->DisplayBareHeader($oPage, $bEditMode);
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTab('UI:PropertiesTab');
$this->DisplayBareProperties($oPage, $bEditMode);
}
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
if ($bEditMode) return array(); // Not editable
$aDetails = array();
$sClass = get_class($this);
$aZList = MetaModel::FlattenZlist(MetaModel::GetZListItems($sClass, 'details'));
foreach ($aZList as $sAttCode) {
$sDisplayValue = $this->GetAsHTML($sAttCode);
$aDetails[] = array('label' => '<span title="'.MetaModel::GetDescription($sClass, $sAttCode).'">'.MetaModel::GetLabel($sClass, $sAttCode).'</span>', 'value' => $sDisplayValue);
}
$oPage->Details($aDetails);
return array();
}
}
class EventNotification extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_notification",
"db_key_field" => "id",
"db_finalclass_field" => "",
"order_by_default" => array('date' => false),
'indexes' => array(
array( 'object_class', 'object_id'),
)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> "", "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass" => "Action", "jointype" => "", "allowed_values" => null, "sql" => "action_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeInteger("object_id", array("allowed_values" => null, "sql" => "object_id", "default_value" => 0, "is_null_allowed" => false, "depends_on" => array())));
//@since 3.2.0
MetaModel::Init_AddAttribute(new AttributeClass("object_class", array("class_category"=>"", "more_values"=>"", "sql"=>"object_class", "default_value"=>null, "is_null_allowed"=>true /*to avoid setting AbstractResource as default in database*/, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'message', 'userinfo', 'trigger_id', 'action_id', 'object_class', 'object_id')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'message')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
class EventNotificationEmail extends EventNotification
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeHTML("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTable("attachments", array("allowed_values"=>null, "sql"=>"attachments", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'message', 'trigger_id', 'action_id', 'object_class', 'object_id', 'to', 'cc', 'bcc', 'from', 'subject', 'body', 'attachments')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'message', 'to', 'subject', 'attachments')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
class EventIssue extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_issue",
"db_key_field" => "id",
"db_finalclass_field" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("issue", array("allowed_values"=>null, "sql"=>"issue", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("page", array("allowed_values"=>null, "sql"=>"page", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributePropertySet("arguments_post", array("allowed_values"=>null, "sql"=>"arguments_post", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributePropertySet("arguments_get", array("allowed_values"=>null, "sql"=>"arguments_get", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTable("callstack", array("allowed_values"=>null, "sql"=>"callstack", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributePropertySet("data", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'message', 'userinfo', 'issue', 'impact', 'page', 'arguments_post', 'arguments_get', 'callstack', 'data')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'issue', 'impact')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
protected function OnInsert()
{
// Init page information: name, arguments
//
$this->Set('page', @$GLOBALS['_SERVER']['SCRIPT_NAME']);
if (strlen($this->Get('userinfo')) == 0) {
$this->Set('userinfo', UserRights::GetUserId());
}
if (array_key_exists('_GET', $GLOBALS) && is_array($GLOBALS['_GET']))
{
$this->Set('arguments_get', $this->SanitizeRequestParams($GLOBALS['_GET']));
}
else
{
$this->Set('arguments_get', array());
}
if (array_key_exists('_POST', $GLOBALS) && is_array($GLOBALS['_POST']))
{
$this->Set('arguments_post', $this->SanitizeRequestParams($GLOBALS['_POST']));
} else {
$this->Set('arguments_post', array());
}
$sLength = mb_strlen($this->Get('issue'));
if ($sLength > 255) {
$this->Set('issue', mb_substr($this->Get('issue'), 0, 210)." -truncated ($sLength chars)");
}
$sLength = mb_strlen($this->Get('impact'));
if ($sLength > 255) {
$this->Set('impact', mb_substr($this->Get('impact'), 0, 210)." -truncated ($sLength chars)");
}
$sLength = mb_strlen($this->Get('page'));
if ($sLength > 255) {
$this->Set('page', mb_substr($this->Get('page'), 0, 210)." -truncated ($sLength chars)");
}
}
protected function SanitizeRequestParams(array $aParams): array
{
$aSanitizedParams = [];
foreach ($aParams as $sKey => $sValue) {
if (is_string($sValue)) {
if (stristr($sKey, 'pwd') !== false || stristr($sKey, 'passwd') !== false || stristr($sKey, 'password') !== false) {
$aSanitizedParams[$sKey] = '****';
} elseif (mb_strlen($sValue) < 256) {
$aSanitizedParams[$sKey] = $sValue;
} else {
$aSanitizedParams[$sKey] = '!long string: '.mb_strlen($sValue).' chars';
}
} else {
// Not a string (avoid warnings in case the value cannot be easily cast into a string)
$aSanitizedParams[$sKey] = @(string)$sValue;
}
}
return $aSanitizedParams;
}
}
class EventWebService extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_webservice",
"db_key_field" => "id",
"db_finalclass_field" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("verb", array("allowed_values"=>null, "sql"=>"verb", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
//MetaModel::Init_AddAttribute(new AttributeStructure("arguments", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeBoolean("result", array("allowed_values"=>null, "sql"=>"result", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("log_info", array("allowed_values"=>null, "sql"=>"log_info", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("log_warning", array("allowed_values"=>null, "sql"=>"log_warning", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("log_error", array("allowed_values"=>null, "sql"=>"log_error", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("data", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'verb', 'result', 'log_info', 'log_warning', 'log_error', 'data')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'verb', 'result')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
class EventRestService extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_restservice",
"db_key_field" => "id",
"db_finalclass_field" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("operation", array("allowed_values"=>null, "sql"=>"operation", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("json_input", array("allowed_values"=>null, "sql"=>"json_input", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("code", array("allowed_values"=>null, "sql"=>"code", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("json_output", array("allowed_values"=>null, "sql"=>"json_output", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("provider", array("allowed_values"=>null, "sql"=>"provider", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'operation', 'version', 'json_input', 'message', 'code', 'json_output', 'provider')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'operation', 'message')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
class EventLoginUsage extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_loginusage",
"db_key_field" => "id",
"db_finalclass_field" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
$aZList = array('date', 'user_id');
if (MetaModel::IsValidAttCode('Contact', 'name'))
{
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"contactid", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList[] = 'contact_name';
}
if (MetaModel::IsValidAttCode('Contact', 'email'))
{
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList[] = 'contact_email';
}
// Display lists
MetaModel::Init_SetZListItems('details', array_merge($aZList, array('userinfo', 'message'))); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array_merge($aZList, array('userinfo'))); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', $aZList); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
class EventOnObject extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_onobject",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("obj_class", array("allowed_values"=>null, "sql"=>"obj_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("obj_key", array("allowed_values"=>null, "sql"=>"obj_key", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'obj_class', 'obj_key', 'message')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'obj_class', 'obj_key', 'message')); // Attributes to be displayed for a list
}
}

View File

@@ -110,7 +110,7 @@ class InlineImage extends DBObject
$aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
$iOrgId = $oItem->Get($sAttCode);
@@ -146,7 +146,7 @@ class InlineImage extends DBObject
$aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
// Second: check that the organization CAN be fetched from the current user
@@ -156,7 +156,7 @@ class InlineImage extends DBObject
$aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
// OK - try it
@@ -192,7 +192,7 @@ class InlineImage extends DBObject
if (!is_null($iTransactionId))
{
// Attach new (temporary) inline images
$sTempId = utils::GetUploadTempId($iTransactionId);
// The object is being created from a form, check if there are pending inline images for this object
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
@@ -366,10 +366,10 @@ CombodoInlineImage.FixImagesWidth();
JS
;
}
return $sJS;
}
/**
* Check if an the given mimeType is an image that can be processed by the system
*
@@ -382,7 +382,7 @@ JS
public static function IsImage($sMimeType)
{
if (!function_exists('gd_info')) return false; // no image processing capability on this system
$bRet = false;
$aInfo = gd_info(); // What are the capabilities
switch($sMimeType)
@@ -390,19 +390,19 @@ JS
case 'image/gif':
return $aInfo['GIF Read Support'];
break;
case 'image/jpeg':
return $aInfo['JPEG Support'];
break;
case 'image/png':
return $aInfo['PNG Support'];
break;
}
return $bRet;
}
/**
* Resize an image so that it fits the maximum width/height defined in the config file
* @param ormDocument $oImage The original image stored as an array (content / mimetype / filename)
@@ -444,7 +444,7 @@ JS
}
return $sRet;
}
/**
* Get the fragment of javascript needed to complete the initialization of
* CKEditor when creating/modifying an object

View File

@@ -26,14 +26,7 @@ require_once APPROOT.'core/modulehandler.class.inc.php';
require_once APPROOT.'core/querymodifier.class.inc.php';
require_once APPROOT.'core/metamodelmodifier.inc.php';
require_once APPROOT.'core/computing.inc.php';
//require_once APPROOT.'core/relationgraph.class.inc.php';
require_once(APPROOT.'/sources/Core/RelationGraph/RelationEdge.php');
require_once(APPROOT.'/sources/Core/RelationGraph/RelationGraph.php');
require_once(APPROOT.'/sources/Core/RelationGraph/RelationObjectNode.php');
require_once(APPROOT.'/sources/Core/RelationGraph/RelationRedundancyNode.php');
require_once APPROOT.'core/relationgraph.class.inc.php';
require_once APPROOT.'core/apc-compat.php';
require_once APPROOT.'core/expressioncache.class.inc.php';
@@ -406,6 +399,32 @@ abstract class MetaModel
return null;
}
final public static function GetDefaultWorkingTime()
{
return 'DefaultWorkingTimeComputer';
}
/**
* @param string $sClass
*
* @return string the working time class
* @throws \CoreException
* @since 3.3.0
*/
final public static function GetWorkingTime($sClass)
{
self::_check_subclass($sClass);
if (array_key_exists('working_time', self::$m_aClassParams[$sClass])) {
return self::$m_aClassParams[$sClass]['working_time'];
} else {
$sParentClass = self::GetParentPersistentClass($sClass);
if (strlen($sParentClass) > 0) {
return self::GetWorkingTime($sParentClass);
}
}
return self::GetDefaultWorkingTime();
}
/**
* @param string $sClass
*
@@ -2112,55 +2131,6 @@ abstract class MetaModel
*/
private static $m_aRelationInfos = array();
/**
* @deprecated Use EnumRelationsEx instead
*
* @param string $sClass
*
* @return array multitype:string unknown |Ambigous <string, multitype:>
* @throws \CoreException
* @throws \Exception
* @throws \OQLException
*/
public static function EnumRelations($sClass = '')
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('Use EnumRelationsEx instead');
$aResult = array_keys(self::$m_aRelationInfos);
if (!empty($sClass)) {
// Return only the relations that have a meaning (i.e. for which at least one query is defined)
// for the specified class
$aClassRelations = array();
foreach ($aResult as $sRelCode) {
$aQueriesDown = self::EnumRelationQueries($sClass, $sRelCode);
if (count($aQueriesDown) > 0) {
$aClassRelations[] = $sRelCode;
}
// Temporary patch: until the impact analysis GUI gets rewritten,
// let's consider that "depends on" is equivalent to "impacts/up"
// The current patch has been implemented in DBObject and MetaModel
if ($sRelCode == 'impacts') {
$aQueriesUp = self::EnumRelationQueries($sClass, 'impacts', false);
if (count($aQueriesUp) > 0)
{
$aClassRelations[] = 'depends on';
}
}
}
return $aClassRelations;
}
// Temporary patch: until the impact analysis GUI gets rewritten,
// let's consider that "depends on" is equivalent to "impacts/up"
// The current patch has been implemented in DBObject and MetaModel
if (in_array('impacts', $aResult))
{
$aResult[] = 'depends on';
}
return $aResult;
}
/**
* @param string $sClass
*
@@ -5150,7 +5120,7 @@ abstract class MetaModel
*/
protected static function DBCreateViews()
{
[$aErrors, $aSugFix] = self::DBCheckViews();
[$aErrors, $aSugFix] = self::DBCleanLegacyViews();
foreach($aSugFix as $sClass => $aTarget)
{
@@ -5796,18 +5766,18 @@ abstract class MetaModel
/**
* @deprecated 2.7.0 N°2369 Method will not be removed any time soon as we still need to drop view if the instance is migrating from an iTop 2.x to an iTop 3.0 or newer, even if they skip iTop 3.0.
* @since 3.0.0 Does not recreate SQL views, only drops them. Method has not been renamed to avoid regressions
* @internal
* @deprecated will be remove when 2.7 will not be supported anymore
*
* @return array
* @throws \CoreException
* @throws \Exception
* @throws \MissingQueryArgument
*/
public static function DBCheckViews()
public static function DBCleanLegacyViews()
{
$aErrors = array();
$aSugFix = array();
$aErrors = [];
$aSugFix = [];
// Reporting views (must be created after any other table)
//
@@ -5822,7 +5792,7 @@ abstract class MetaModel
}
}
return array($aErrors, $aSugFix);
return [$aErrors, $aSugFix];
}
/**
@@ -7034,7 +7004,7 @@ abstract class MetaModel
* @param array $aParams
* @param bool $bAllowAllData
*
* @return \DBObject
* @return \DBObject|null
* @throws \OQLException
*/
public static function GetObjectFromOQL($sQuery, $aParams = null, $bAllowAllData = false)
@@ -7516,42 +7486,6 @@ abstract class MetaModel
SetupUtils::rrmdir(utils::GetCachePath($sEnvironment));
}
/**
* @internal
* @param string $sEnvironmentId
* @deprecated 3.2.1
*/
public static function ResetCache($sEnvironmentId = null)
{
if (is_null($sEnvironmentId))
{
$sEnvironmentId = MetaModel::GetEnvironmentId();
}
$sAppIdentity = 'itop-'.$sEnvironmentId;
require_once(APPROOT.'/core/dict.class.inc.php');
Dict::ResetCache($sAppIdentity);
if (function_exists('apc_delete'))
{
foreach(self::GetCacheEntries($sEnvironmentId) as $sKey => $aAPCInfo)
{
$sAPCKey = $aAPCInfo['info'];
apc_delete($sAPCKey);
}
}
require_once(APPROOT.'core/userrights.class.inc.php');
UserRights::FlushPrivileges();
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
if (function_exists('opcache_reset'))
{
// Zend opcode cache
opcache_reset();
}
}
/**
* Given a field spec, get the most relevant (unique) representation
* Examples for a user request:
@@ -7716,7 +7650,6 @@ abstract class MetaModel
'iLoginUIExtension',
'iPreferencesExtension',
'iApplicationUIExtension',
'iApplicationObjectExtension',
'iPopupMenuExtension',
'iPageUIBlockExtension',
'iBackofficeLinkedScriptsExtension',

View File

@@ -495,8 +495,8 @@ class OqlObjectQuery extends OqlQuery
{
throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
}
$aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeExternalKey');
$aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeObjectKey');
$aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey::class);
$aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeObjectKey::class);
$aAllKeys = array_merge($aExtKeys, $aObjKeys);
if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
{
@@ -557,7 +557,7 @@ class OqlObjectQuery extends OqlQuery
}
$aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
$sAttType = $aAttList[$sExtKeyAttCode];
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, 'AttributeHierarchicalKey') && ($sAttType != 'AttributeHierarchicalKey'))
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class) && ($sAttType != \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class))
{
throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
}

View File

@@ -17,7 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
*/
require_once(APPROOT.'core/dbobjectiterator.php');
require_once('dbobjectiterator.php');
/**
@@ -240,25 +240,6 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
return $aRet;
}
/**
* @param bool $bWithId
* @return array
* @deprecated Since iTop 2.4, use foreach($this as $oItem){} instead
*/
public function ToArray($bWithId = true)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use foreach($this as $oItem){} instead');
$aRet = array();
foreach ($this as $oItem) {
if ($bWithId) {
$aRet[$oItem->GetKey()] = $oItem;
} else {
$aRet[] = $oItem;
}
}
return $aRet;
}
/**
* @param string $sAttCode
* @param bool $bWithId
@@ -532,6 +513,17 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
{
$bUpdateFromDelta = true;
}
} else {
//@since 3.2.2 N°2364 - API : remove old linkedset persistance
/* Goo pattern to use:
* $oCISet = $oTicket->Get(functioncis_list);
* $oCISet->AddItem(MetaModel::NewObject(lnkFunctionCIToTicket, array(ci_id=> 12345));
* $oCISet->RemoveItem(123456);
* $oTicket->Set(functionalcis_list, $oCISet);
*/
if (!ContextTag::Check(ContextTag::TAG_SETUP)) {
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('old pattern - please get previous value of the linked set, modify it and set it back to the host object');
}
}
if ($bUpdateFromDelta)
@@ -547,7 +539,6 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
else
{
// For backward compatibility reasons, let's rebuild a delta...
// Reset the delta
$this->iCursor = 0;
$this->aAdded = array();

View File

@@ -17,13 +17,7 @@
// along with iTop. If not, see <http://www.gnu.org/licenses/>
require_once(APPROOT.'/sources/Application/SimpleCrypt/CryptEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCrypt.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptMcryptEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptOpenSSLEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptOpenSSLMcryptCompatibilityEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptSimpleEngine.php');
require_once(APPROOT.'/sources/Application/SimpleCrypt/SimpleCryptSodiumEngine.php');
require_once(APPROOT.'/core/simplecrypt.class.inc.php');
/**
* ormPassword

View File

@@ -16,7 +16,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/>
require_once(APPROOT.'core/backgroundprocess.inc.php');
require_once('backgroundprocess.inc.php');
/**
* ormStopWatch
@@ -289,7 +289,7 @@ class ormStopWatch
$sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
if ($sWorkingTimeComputer == '')
{
$sWorkingTimeComputer = class_exists('SLAComputation') ? 'SLAComputation' : 'DefaultWorkingTimeComputer';
$sWorkingTimeComputer = MetaModel::GetWorkingTime(get_class($oObject));
}
$oComputer = new $sWorkingTimeComputer();
$aCallSpec = array($oComputer, 'GetDeadline');
@@ -318,7 +318,7 @@ class ormStopWatch
$sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
if ($sWorkingTimeComputer == '')
{
$sWorkingTimeComputer = class_exists('SLAComputation') ? 'SLAComputation' : 'DefaultWorkingTimeComputer';
$sWorkingTimeComputer = MetaModel::GetWorkingTime(get_class($oObject));
}
$oComputer = new $sWorkingTimeComputer();
$aCallSpec = array($oComputer, 'GetOpenDuration');

View File

@@ -1,4 +1,21 @@
<?php
// Copyright (C) 2015-2024 Combodo SAS
//
// This file is part of iTop.
//
// 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.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Data structures (i.e. PHP classes) to build and use relation graphs
@@ -8,6 +25,169 @@
*
*/
require_once(APPROOT.'core/simplegraph.class.inc.php');
/**
* An object Node inside a RelationGraph
*/
class RelationObjectNode extends GraphNode
{
public function __construct($oGraph, $oObject)
{
parent::__construct($oGraph, self::MakeId($oObject));
$this->SetProperty('object', $oObject);
$this->SetProperty('label', get_class($oObject).'::'.$oObject->GetKey().' ('.$oObject->Get('friendlyname').')');
}
/**
* Make a normalized ID to ensure the uniqueness of such a node
*
* @param string $oObject
*
* @return string
*/
public static function MakeId($oObject)
{
return get_class($oObject).'::'.$oObject->GetKey();
}
/**
* Formatting for GraphViz
*
* @param bool $bNoLabel
*
* @return string
*/
public function GetDotAttributes($bNoLabel = false)
{
$sDot = parent::GetDotAttributes();
if ($this->GetProperty('developped', false))
{
$sDot .= ',fontcolor=black';
}
else
{
$sDot .= ',fontcolor=lightgrey';
}
if ($this->GetProperty('source', false) || $this->GetProperty('sink', false))
{
$sDot .= ',shape=rectangle';
}
if ($this->GetProperty('is_reached', false))
{
$sDot .= ',fillcolor="#ffdddd"';
}
else
{
$sDot .= ',fillcolor=white';
}
return $sDot;
}
/**
* Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node or a 'not allowed' node
*
* @param string $sProperty
* @param $value
*/
public function ReachDown($sProperty, $value)
{
if (is_null($this->GetProperty($sProperty)) && ($this->GetProperty($sProperty.'_allowed') !== false))
{
$this->SetProperty($sProperty, $value);
foreach ($this->GetOutgoingEdges() as $oOutgoingEdge)
{
// Recurse
$oOutgoingEdge->GetSinkNode()->ReachDown($sProperty, $value);
}
}
}
}
/**
* An redundancy Node inside a RelationGraph
*/
class RelationRedundancyNode extends GraphNode
{
public function __construct($oGraph, $sId, $iMinUp, $fThreshold)
{
parent::__construct($oGraph, $sId);
$this->SetProperty('min_up', $iMinUp);
$this->SetProperty('threshold', $fThreshold);
}
/**
* Make a normalized ID to ensure the uniqueness of such a node
*
* @param string $sRelCode
* @param string $sNeighbourId
* @param $oSourceObject
* @param \DBObject $oSinkObject
*
* @return string
*/
public static function MakeId($sRelCode, $sNeighbourId, $oSourceObject, $oSinkObject)
{
return 'redundancy-'.$sRelCode.'-'.$sNeighbourId.'-'.get_class($oSinkObject).'::'.$oSinkObject->GetKey();
}
/**
* Formatting for GraphViz
*
* @param bool $bNoLabel
*
* @return string
*/
public function GetDotAttributes($bNoLabel = false)
{
$sDisplayThreshold = sprintf('%.1f', $this->GetProperty('threshold'));
$sDot = 'shape=doublecircle,fillcolor=indianred,fontcolor=papayawhip,label="'.$sDisplayThreshold.'"';
return $sDot;
}
/**
* Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node
*
* @param string $sProperty
* @param $value
*/
public function ReachDown($sProperty, $value)
{
$this->SetProperty($sProperty.'_count', $this->GetProperty($sProperty.'_count', 0) + 1);
if ($this->GetProperty($sProperty.'_count') > $this->GetProperty('threshold'))
{
// Looping... though there should be only ONE SINGLE outgoing edge
foreach ($this->GetOutgoingEdges() as $oOutgoingEdge)
{
// Recurse
$oOutgoingEdge->GetSinkNode()->ReachDown($sProperty, $value);
}
}
}
}
/**
* Helper to name the edges in a unique way
*/
class RelationEdge extends GraphEdge
{
/**
* RelationEdge constructor.
*
* @param \SimpleGraph $oGraph
* @param \GraphNode $oSourceNode
* @param \GraphNode $oSinkNode
* @param bool $bMustBeUnique
*
* @throws \SimpleGraphException
*/
public function __construct(SimpleGraph $oGraph, GraphNode $oSourceNode, GraphNode $oSinkNode, $bMustBeUnique = false)
{
$sId = $oSourceNode->GetId().'-to-'.$oSinkNode->GetId();
parent::__construct($oGraph, $sId, $oSourceNode, $oSinkNode, $bMustBeUnique);
}
}
/**
* A graph representing the relations between objects

View File

@@ -0,0 +1,431 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// 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.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* SimpleCrypt Class - crypto helpers
* Simple encryption of strings, uses mcrypt or degrades to a pure PHP
* implementation when mcrypt is not present.
* Based on Miguel Ros' work found at:
* http://rossoft.wordpress.com/2006/05/22/simple-encryption-class/
*
* Usage:
* $oSimpleCrypt = new SimpleCrypt();
* $encrypted = $oSimpleCrypt->encrypt('a_key','the_text');
* $sClearText = $oSimpleCrypt->decrypt('a_key',$encrypted);
*
* The result is $plain equals to 'the_text'
*
* You can use a different engine if you don't have Mcrypt:
* $oSimpleCrypt = new SimpleCrypt('Simple');
*
* A string encrypted with one engine can't be decrypted with
* a different one even if the key is the same.
*
* @author Miguel Ros <rossoft@gmail.com>
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class SimpleCrypt
{
/**
* @var \SimpleCrypt
* @since 3.1.0 N°5388
*/
protected $oEngine;
public static function GetNewDefaultParams()
{
if(function_exists('sodium_crypto_secretbox_open') && function_exists('random_bytes')){
$sEngineName = 'Sodium';
}
else if (function_exists('openssl_decrypt'))
{
$sEngineName = 'OpenSSL';
}
else if(function_exists('mcrypt_module_open')){
$sEngineName = 'Mcrypt';
}
else
{
$sEngineName = 'Simple';
}
$sEngineName = 'SimpleCrypt' . $sEngineName . 'Engine';
return $sEngineName::GetNewDefaultParams();
}
/**
* Constructor
* @param string $sEngineName Engine for encryption. Values: Simple, Mcrypt, Sodium or OpenSSL
* @throws Exception This library is unkown
*/
function __construct($sEngineName = 'Mcrypt')
{
switch($sEngineName){
case 'Sodium':
if(!function_exists('sodium_crypto_secretbox_open')){
$sEngineName = 'Simple';
}
break;
case 'Mcrypt':
if(!function_exists('mcrypt_module_open')){
if (function_exists('openssl_decrypt'))
{
$sEngineName = 'OpenSSLMcryptCompatibility';
}
else
{
$sEngineName = 'Simple';
}
}
break;
case 'OpenSSL':
case 'OpenSSLMcryptCompatibility':
if(!function_exists('openssl_decrypt')){
$sEngineName = 'Simple';
}
break;
case 'Simple':
break;
default:
throw new Exception(Dict::Format("Core:AttributeEncryptUnknownLibrary", $sEngineName));
}
$sEngineName = 'SimpleCrypt' . $sEngineName . 'Engine';
$this->oEngine = new $sEngineName;
}
/**
* Encrypts the string with the given key
* @param string $key
* @param string $sString Plaintext string
* @return string Ciphered string
*/
function Encrypt($key, $sString)
{
return $this->oEngine->Encrypt($key,$sString);
}
/**
* Decrypts the string by the given key
* @param string $key
* @param string $string Ciphered string
* @return string Plaintext string
*/
function Decrypt($key, $string)
{
if (is_null($string) || strlen($string) == 0) {
IssueLog::Warning("Cannot decrypt empty/null value");
return $string;
}
return $this->oEngine->Decrypt($key, $string);
}
/**
* Returns a random "salt" value, to be used when "hashing" a password
* using a one-way encryption algorithm, to prevent an attack using a "rainbow table"
* Tryes to use the best available random number generator
* @return string The generated random "salt"
*/
static function GetNewSalt()
{
// Copied from http://www.php.net/manual/en/function.mt-rand.php#83655
// get 128 pseudorandom bits in a string of 16 bytes
$sRandomBits = null;
// Unix/Linux platform?
$fp = @fopen('/dev/urandom','rb');
if ($fp !== FALSE)
{
//echo "Random bits pulled from /dev/urandom<br/>\n";
$sRandomBits .= @fread($fp,16);
@fclose($fp);
}
else
{
// MS-Windows platform?
if (@class_exists('COM'))
{
// http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
try
{
$CAPI_Util = new COM('CAPICOM.Utilities.1');
$sBase64RandomBits = ''.$CAPI_Util->GetRandom(16,0);
// if we ask for binary data PHP munges it, so we
// request base64 return value. We squeeze out the
// redundancy and useless ==CRLF by hashing...
if ($sBase64RandomBits)
{
//echo "Random bits got from CAPICOM.Utilities.1<br/>\n";
$sRandomBits = md5($sBase64RandomBits, TRUE);
}
}
catch (Exception $ex)
{
// echo 'Exception: ' . $ex->getMessage();
}
}
}
if ($sRandomBits == null)
{
// No "strong" random generator available, use PHP's built-in mechanism
//echo "Random bits generated from mt_rand<br/>\n";
mt_srand(crc32(microtime()));
$sRandomBits = '';
for($i = 0; $i < 4; $i++)
{
$sRandomBits .= sprintf('%04x', mt_rand(0, 65535));
}
}
return $sRandomBits;
}
}
/**
* Interface for encryption engines
*/
interface CryptEngine
{
public static function GetNewDefaultParams();
function Encrypt($key, $sString);
function Decrypt($key, $encrypted_data);
}
/**
* Simple Engine doesn't need any PHP extension.
* Every encryption of the same string with the same key
* will return the same encrypted string
*/
class SimpleCryptSimpleEngine implements CryptEngine
{
public static function GetNewDefaultParams()
{
return array( 'lib' => 'Simple', 'key' => null);
}
public function Encrypt($key, $sString)
{
$result = '';
for($i=1; $i<=strlen($sString); $i++)
{
$char = substr($sString, $i-1, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)+ord($keychar));
$result.=$char;
}
return $result;
}
public function Decrypt($key, $encrypted_data)
{
$result = '';
for($i=1; $i<=strlen($encrypted_data); $i++)
{
$char = substr($encrypted_data, $i-1, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)-ord($keychar));
$result.=$char;
}
return $result;
}
}
/**
* McryptEngine requires Mcrypt extension
* Every encryption of the same string with the same key
* will return a different encrypted string.
*/
class SimpleCryptMcryptEngine implements CryptEngine
{
var $alg = MCRYPT_BLOWFISH;
var $td = null;
public static function GetNewDefaultParams()
{
return array('lib' => 'Mcrypt', 'key' => null);
}
public function __construct()
{
$this->td = mcrypt_module_open($this->alg,'','cbc','');
}
public function Encrypt($key, $sString)
{
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($this->td), MCRYPT_DEV_URANDOM); // MCRYPT_DEV_URANDOM is now useable since itop requires php >= 5.6
if (false === $iv) {
throw new Exception('IV generation failed');
}
mcrypt_generic_init($this->td, $key, $iv);
if (empty($sString))
{
$sString = str_repeat("\0", 8);
}
$encrypted_data = mcrypt_generic($this->td, $sString);
mcrypt_generic_deinit($this->td);
return $iv.$encrypted_data;
}
public function Decrypt($key, $encrypted_data)
{
$iv = substr($encrypted_data, 0, mcrypt_enc_get_iv_size($this->td));
$string = substr($encrypted_data, mcrypt_enc_get_iv_size($this->td));
$r = mcrypt_generic_init($this->td, $key, $iv);
if (($r < 0) || ($r === false))
{
$decrypted_data = Dict::S("Core:AttributeEncryptFailedToDecrypt");
}
else
{
$decrypted_data = rtrim(mdecrypt_generic($this->td, $string), "\0");
mcrypt_generic_deinit($this->td);
}
return $decrypted_data;
}
public function __destruct()
{
mcrypt_module_close($this->td);
}
}
/**
* SodiumEngine requires Sodium extension
* Every encryption of the same string with the same key
* will return a different encrypted string.
* The key has to be SODIUM_CRYPTO_SECRETBOX_KEYBYTES bytes long.
*/
class SimpleCryptSodiumEngine implements CryptEngine
{
public static function GetNewDefaultParams()
{
return array('lib' => 'Sodium', 'key' => bin2hex(sodium_crypto_secretbox_keygen()));
}
public function Encrypt($key, $sString)
{
$key = hex2bin($key);
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$encrypted_string = sodium_crypto_secretbox($sString, $nonce, $key);
sodium_memzero($sString);
sodium_memzero($key);
return base64_encode($nonce.$encrypted_string);
}
public function Decrypt($key, $encrypted_data)
{
$key = hex2bin($key);
$encrypted_data = base64_decode($encrypted_data);
$nonce = mb_substr($encrypted_data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$encrypted_data = mb_substr($encrypted_data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plaintext = sodium_crypto_secretbox_open($encrypted_data, $nonce, $key);
if ($plaintext === false)
{
$plaintext = Dict::S("Core:AttributeEncryptFailedToDecrypt");
}
sodium_memzero($encrypted_data);
sodium_memzero($key);
return $plaintext;
}
}
class SimpleCryptOpenSSLEngine implements CryptEngine
{
public static function GetNewDefaultParams()
{
return array('lib' => 'OpenSSL', 'key' => bin2hex(openssl_random_pseudo_bytes(32)));
}
public function Encrypt($key, $sString)
{
$key = hex2bin($key);
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"));
$encrypted_string = openssl_encrypt($sString, "AES-256-CBC", $key, 0 , $iv);
return $iv.$encrypted_string;
}
public function Decrypt($key, $encrypted_data)
{
$key = hex2bin($key);
$iv = mb_substr($encrypted_data, 0, openssl_cipher_iv_length("AES-256-CBC"), '8bit');
$encrypted_data = mb_substr($encrypted_data, openssl_cipher_iv_length("AES-256-CBC"), null, '8bit');
$plaintext = openssl_decrypt($encrypted_data,"AES-256-CBC", $key, 0 , $iv);
if ($plaintext === false)
{
$plaintext = Dict::S("Core:AttributeEncryptFailedToDecrypt");
}
return trim($plaintext);
}
}
class SimpleCryptOpenSSLMcryptCompatibilityEngine implements CryptEngine
{
public static function GetNewDefaultParams()
{
return array('lib' => 'OpenSSLMcryptCompatibility', 'key' => null);
}
//fix for php < 7.1.8 (keys are Zero padded instead of cycle padded)
static private function MakeOpenSSLBlowfishKey($key)
{
if("$key" === '')
{
return $key;
}
$len = (16+2)*4;
while(strlen($key) < $len)
{
$key .= $key;
}
$key = substr($key, 0, $len);
return $key;
}
public function Encrypt($key, $sString)
{
$key = SimpleCryptOpenSSLMcryptCompatibilityEngine::MakeOpenSSLBlowfishKey($key);
$blockSize = 8;
$len = strlen($sString);
$paddingLen = intval (($len + $blockSize -1) / $blockSize) * $blockSize - $len;
$padding = str_repeat("\0", $paddingLen);
$sData = $sString . $padding;
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("BF-CBC"));
$encrypted_string = openssl_encrypt($sData, "BF-CBC", $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
return $iv.$encrypted_string;
}
public function Decrypt($key, $encrypted_data)
{
$key = SimpleCryptOpenSSLMcryptCompatibilityEngine::MakeOpenSSLBlowfishKey($key);
$iv = mb_substr($encrypted_data, 0, openssl_cipher_iv_length("BF-CBC"), '8bit');
$encrypted_data = mb_substr($encrypted_data, openssl_cipher_iv_length("BF-CBC"), null, '8bit');
$plaintext = openssl_decrypt($encrypted_data,"BF-CBC", $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
if ($plaintext === false)
{
$plaintext = Dict::S("Core:AttributeEncryptFailedToDecrypt");
}
return trim($plaintext);
}
}

894
core/trigger.class.inc.php Normal file
View File

@@ -0,0 +1,894 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
* This file is part of iTop.
*
* 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.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
/**
* A user defined trigger, to customize the application
* A trigger will activate an action
*
* @package iTopORM
*/
abstract class Trigger extends cmdbAbstractObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-conflict.svg'),
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("action_list",
array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "trigger_id", "ext_key_to_remote" => "action_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array())));
$aTags = ContextTag::GetTags();
MetaModel::Init_AddAttribute(new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags, true), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12)));
// "complement" is a computed field, fed by Trigger sub-classes, in general in ComputeValues method, for eg. the TriggerOnObject fed it with target_class info
MetaModel::Init_AddAttribute(new AttributeString("complement", array("allowed_values" => null, "sql" => "complement", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("subscription_policy", array("allowed_values" => new ValueSetEnum(Combodo\iTop\Core\Trigger\Enum\SubscriptionPolicy::cases()), "sql" => "subscription_policy", "default_value" => \Combodo\iTop\Core\Trigger\Enum\SubscriptionPolicy::AllowNoChannel->value, "is_null_allowed" => false, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('finalclass', 'description', 'context', 'subscription_policy', 'action_list', 'complement')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'complement')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
/**
* Check if the trigger can be used in the current context
*
* @return bool true if context OK
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function IsContextValid()
{
// Check the context
$oContext = $this->Get('context');
$bChecked = false;
$bValid = false;
foreach ($oContext->GetValues() as $sValue)
{
$bChecked = true;
if (ContextTag::Check($sValue))
{
$bValid = true;
break;
}
}
if ($bChecked && !$bValid)
{
// Trigger does not match the current context
return false;
}
return true;
}
/**
* @param $aContextArgs
*
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function DoActivate($aContextArgs)
{
// Check the context
if (!$this->IsContextValid())
{
// Trigger does not match the current context
$sClass = get_class($this);
$sName = $this->Get('friendlyname');
IssueLog::Debug("Context NOT valid for : {$sClass} '$sName'");
return;
}
$aContextArgs['trigger->object()'] = $this;
// Find the related actions
$oLinkedActions = $this->Get('action_list');
// Order actions as expected
$aActionListOrdered = [];
while ($oLink = $oLinkedActions->Fetch()) {
$aActionListOrdered[(int) $oLink->Get('order')][] = $oLink;
}
ksort($aActionListOrdered);
// Execute actions
foreach ($aActionListOrdered as $aActionSubList) {
foreach ($aActionSubList as $oLink) /** @var \DBObject $oLink */ {
/** @var \DBObject $oLink */
$iActionId = $oLink->Get('action_id');
/** @var \Action $oAction */
$oAction = MetaModel::GetObject('Action', $iActionId);
if ($oAction->IsActive()) {
$oKPI = new ExecutionKPI();
$aContextArgs['action->object()'] = $oAction;
$oAction->DoExecute($this, $aContextArgs);
$oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
}
}
}
}
/**
* Check whether the given object is in the scope of this trigger
* and can potentially be the subject of notifications
*
* @param DBObject $oObject The object to check
*
* @return bool
*/
public function IsInScope(DBObject $oObject)
{
// By default the answer is no
// Overload this function in your own derived class for a different behavior
return false;
}
}
/**
* Class TriggerOnObject
*/
abstract class TriggerOnObject extends Trigger
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onobject",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeClass("target_class",
array("class_category" => "bizmodel", "more_values" => "User,UserExternal,UserInternal,UserLDAP,UserLocal", "sql" => "target_class", "default_value" => null, "is_null_allowed" => false, "depends_on" => array(), "class_exclusion_list" => "Attachment")));
MetaModel::Init_AddAttribute(new AttributeOQL("filter", array("allowed_values" => null, "sql" => "filter", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'description')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('default_search', array('description', 'target_class')); // Default criteria of the search banner
// MetaModel::Init_SetZListItems('standard_search', array('name', 'target_class', 'description')); // Criteria of the search form
}
/**
* @throws \CoreException
*/
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
$sFilter = trim($this->Get('filter') ?? '');
if (strlen($sFilter) > 0)
{
try
{
$oSearch = DBObjectSearch::FromOQL($sFilter);
if (!MetaModel::IsParentClass($this->Get('target_class'), $oSearch->GetClass()))
{
$this->m_aCheckIssues[] = Dict::Format('TriggerOnObject:WrongFilterClass', $this->Get('target_class'));
}
} catch (OqlException $e)
{
$this->m_aCheckIssues[] = Dict::Format('TriggerOnObject:WrongFilterQuery', $e->getMessage());
}
}
}
/**
* @throws \CoreException
*/
public function ComputeValues()
{
parent::ComputeValues();
// Complementary name of a Trigger is manually built
// - the Trigger finalclass code not translated
// - an hardcoded text in english
// - the target class code not translated for TriggerOnObject subclasses
$this->Set('complement', 'class restriction: '.$this->Get('target_class'));
}
/**
* Check whether the given object is in the scope of this trigger
* and can potentially be the subject of notifications
*
* @param DBObject $oObject The object to check
*
* @return bool
* @throws \CoreException
*/
public function IsInScope(DBObject $oObject)
{
$sRootClass = $this->Get('target_class');
return ($oObject instanceof $sRootClass);
}
/**
* @param $aContextArgs
*
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function DoActivate($aContextArgs)
{
$bGo = true;
if (isset($aContextArgs['this->object()']))
{
/** @var \DBObject $oObject */
$oObject = $aContextArgs['this->object()'];
$bGo = $this->IsTargetObject($oObject->GetKey(), $oObject->ListPreviousValuesForUpdatedAttributes());
}
if ($bGo)
{
parent::DoActivate($aContextArgs);
}
}
/**
* if the target class is Attachment, then the trigger is read-only
* @param $sAttCode
* @param $aReasons
* @param $sTargetState
* @return int
* @throws ArchivedObjectException
* @throws CoreException
*/
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState='')
{
// Force the computed field to be read-only, preventing it to be written
if ($this->Get('target_class') == 'Attachment' ) {
return OPT_ATT_READONLY;
}
return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState);
}
public function DisplayBareHeader(WebPage $oPage, $bEditMode = false)
{
$aHeaderBlocks = parent::DisplayBareHeader($oPage, $bEditMode);
if ($this->Get('target_class') == 'Attachment' ) {
$oPage->AddUiBlock(AlertUIBlockFactory::MakeForWarning('', Dict::S('Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage')));
$oPage->add_ready_script("$('#UIMenuModify').hide();");
}
return $aHeaderBlocks;
}
/**
* Activate trigger based on attribute list given instead of changed attributes
*
* @param array $aContextArgs
* @param array|null $aAttributes if null default to changed attributes
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @since 3.1.1 3.2.0 N°6228
*/
public function DoActivateForSpecificAttributes(array $aContextArgs, ?array $aAttributes)
{
if (isset($aContextArgs['this->object()']))
{
/** @var \DBObject $oObject */
$oObject = $aContextArgs['this->object()'];
if (is_null($aAttributes)) {
$aChanges = $oObject->ListPreviousValuesForUpdatedAttributes();
} else {
$aChanges = array_fill_keys($aAttributes, true);
}
if (false === $this->IsTargetObject($oObject->GetKey(), $aChanges)) {
return;
}
}
parent::DoActivate($aContextArgs);
}
/**
* @param $iObjectId
* @param array $aChanges
*
* @return bool True if the object of ID $iObjectId is within the scope of the OQL defined by the "filter" attribute
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function IsTargetObject($iObjectId, $aChanges = array())
{
$sFilter = trim($this->Get('filter') ?? '');
if (strlen($sFilter) > 0) {
$oSearch = DBObjectSearch::FromOQL($sFilter);
$oSearch->AddCondition('id', $iObjectId, '=');
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch);
$bRet = ($oSet->Count() > 0);
} 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);
}
}
/**
* To trigger notifications when a ticket is updated from the portal
*/
class TriggerOnPortalUpdate extends TriggerOnObject
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onportalupdate",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'description')); // Attributes to be displayed for a list
// Search criteria
}
}
/**
* Class TriggerOnStateChange
*/
abstract class TriggerOnStateChange extends TriggerOnObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onstatechange",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeClassState("state", array("class_field" => 'target_class', "allowed_values" => null, "sql" => "state", "default_value" => null, "is_null_allowed" => false, "depends_on" => array('target_class'))));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'state', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'state')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class', 'state')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
/**
* Class TriggerOnStateEnter
*/
class TriggerOnStateEnter extends TriggerOnStateChange
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onstateenter",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'state', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class', 'state')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
/**
* Class TriggerOnStateLeave
*/
class TriggerOnStateLeave extends TriggerOnStateChange
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onstateleave",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'state', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class', 'state')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('')); // Criteria of the advanced search form
}
}
/**
* Class TriggerOnObjectCreate
*/
class TriggerOnObjectCreate extends TriggerOnObject
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onobjcreate",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
/**
* Class TriggerOnObjectCreate
*/
class TriggerOnObjectDelete extends TriggerOnObject
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onobjdelete",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
/**
* Class TriggerOnObjectCreate
*/
class TriggerOnObjectUpdate extends TriggerOnObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onobjupdate",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeClassAttCodeSet('target_attcodes', array("allowed_values" => null, "class_field" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "max_items" => 20, "min_items" => 0, "attribute_definition_exclusion_list" => "AttributeDashboard,AttributeExternalField,AttributeFinalClass,AttributeFriendlyName,AttributeObsolescenceDate,AttributeObsolescenceFlag,AttributeSubItem", "attribute_definition_list" => null, "depends_on" => array('target_class'))));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'target_attcodes', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
}
public function IsTargetObject($iObjectId, $aChanges = array())
{
if (!parent::IsTargetObject($iObjectId, $aChanges))
{
return false;
}
// Check the attribute
$oAttCodeSet = $this->Get('target_attcodes');
$aAttCodes = $oAttCodeSet->GetValues();
if (empty($aAttCodes))
{
return true;
}
foreach($aAttCodes as $sAttCode)
{
if (array_key_exists($sAttCode, $aChanges))
{
return true;
}
}
return false;
}
public function ComputeValues()
{
parent::ComputeValues();
// Remove unwanted attribute codes
$aChanges = $this->ListChanges();
if (isset($aChanges['target_attcodes']))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), 'target_attcodes');
$aArgs = array('this' => $this);
$aAllowedValues = $oAttDef->GetAllowedValues($aArgs);
/** @var \ormSet $oValue */
$oValue = $this->Get('target_attcodes');
$aValues = $oValue->GetValues();
$bChanged = false;
foreach($aValues as $key => $sValue)
{
if (!isset($aAllowedValues[$sValue]))
{
unset($aValues[$key]);
$bChanged = true;
}
}
if ($bChanged)
{
$oValue->SetValues($aValues);
$this->Set('target_attcodes', $oValue);
}
}
}
}
/**
* Class TriggerOnObjectMention
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @since 3.0.0
*/
class TriggerOnObjectMention extends TriggerOnObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onobjmention",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeOQL("mentioned_filter", array("allowed_values" => null, "sql" => "mentioned_filter", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'mentioned_filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
}
/**
* @param \DBObject $oObject
*
* @return bool True if $oObject is within the scope of the OQL defined by the "mentioned_filter" attribute OR if no mentioned_filter defined. Otherwise, returns false.
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function IsMentionedObjectInScope(DBObject $oObject)
{
$sFilter = trim($this->Get('mentioned_filter'));
if (strlen($sFilter) > 0)
{
$oSearch = DBObjectSearch::FromOQL($sFilter);
$sSearchClass = $oSearch->GetClass();
// If filter not on current object class (or descendants), consider it as not in scope
if (is_a($oObject, $sSearchClass, true) === false) {
return false;
}
$oSearch->AddCondition('id', $oObject->GetKey(), '=');
if (MetaModel::IsAbstract($oSearch->GetClass())) {
$oSearch->AddCondition('finalclass', get_class($oObject), '=');
}
$aParams = $oObject->ToArgs('this');
$oSet = new DBObjectSet($oSearch, [], $aParams);
$bRet = $oSet->CountExceeds(0);
}
else
{
$bRet = true;
}
return $bRet;
}
}
/**
* Class TriggerOnAttributeBlobDownload
*
* @since 3.1.0
*/
class TriggerOnAttributeBlobDownload extends TriggerOnObject
{
/**
* @inheritDoc
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_onattblobdownload",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
}
}
/**
* Class lnkTriggerAction
*/
class lnkTriggerAction extends cmdbAbstractObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array('action_id', 'trigger_id'),
"db_table" => "priv_link_action_trigger",
"db_key_field" => "link_id",
"db_finalclass_field" => "",
"is_link" => true,
'uniqueness_rules' => array(
'no_duplicate' => array(
'attributes' => array(
0 => 'action_id',
1 => 'trigger_id',
),
'filter' => '',
'disabled' => false,
'is_blocking' => true,
),
),
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass" => "Action", "jointype" => '', "allowed_values" => null, "sql" => "action_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("action_name", array("allowed_values" => null, "extkey_attcode" => 'action_id', "target_attcode" => "name")));
MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass" => "Trigger", "jointype" => '', "allowed_values" => null, "sql" => "trigger_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("trigger_name", array("allowed_values" => null, "extkey_attcode" => 'trigger_id', "target_attcode" => "description")));
MetaModel::Init_AddAttribute(new AttributeInteger("order", array("allowed_values" => null, "sql" => "order", "default_value" => 0, "is_null_allowed" => true, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('action_id', 'trigger_id', 'order')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', array('action_id', 'trigger_id', 'order')); // Criteria of the advanced search form
}
}
/**
* Class TriggerOnThresholdReached
*/
class TriggerOnThresholdReached extends TriggerOnObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => ['finalclass', 'complement'],
"state_attcode" => "",
"reconc_keys" => ['description'],
"db_table" => "priv_trigger_threshold",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeClassAttCodeSet('stop_watch_code', array("allowed_values" => null, "class_field" => "target_class", "sql" => "stop_watch_code", "default_value" => null, "is_null_allowed" => false, "max_items" => 1, "min_items" => 1, "attribute_definition_exclusion_list" => null, "attribute_definition_list" => "AttributeStopWatch", "include_child_classes_attributes" => true, "depends_on" => array('target_class'))));
MetaModel::Init_AddAttribute(new AttributeString("threshold_index", array("allowed_values" => null, "sql" => "threshold_index", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'stop_watch_code', 'threshold_index', 'filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('target_class', 'threshold_index', 'threshold_index')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}

View File

@@ -142,7 +142,9 @@ abstract class UserRightsAddOnAPI
$oFilter = new DBObjectSearch($sClass);
$oListExpr = ListExpression::FromScalars($aAllowedOrgs);
$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
$oNullCondition = new FunctionExpression('ISNULL', [$oExpression]);
$oInCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
$oCondition = $oNullCondition->LogOr($oInCondition);
$oFilter->AddConditionExpression($oCondition);
if ($this->HasSharing())

View File

@@ -0,0 +1,652 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// 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.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Value set definitions (from a fixed list or from a query, etc.)
*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
require_once('MyHelpers.class.inc.php');
/**
* ValueSetDefinition
* value sets API and implementations
*
* @package iTopORM
*/
abstract class ValueSetDefinition
{
protected $m_bIsLoaded = false;
protected $m_aValues = array();
// Displayable description that could be computed out of the std usage context
public function GetValuesDescription()
{
$aValues = $this->GetValues(array(), '');
$aDisplayedValues = array();
foreach($aValues as $key => $value)
{
$aDisplayedValues[] = "$key => $value";
}
$sAllowedValues = implode(', ', $aDisplayedValues);
return $sAllowedValues;
}
/**
* @param array $aArgs
* @param string $sContains
* @param string $sOperation for the values {@see static::LoadValues()}
*
* @return array hash array of keys => values
*/
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded)
{
$this->LoadValues($aArgs);
$this->m_bIsLoaded = true;
}
if (strlen($sContains) == 0)
{
// No filtering
$aRet = $this->m_aValues;
}
else
{
// Filter on results containing the needle <sContain>
$aRet = array();
foreach ($this->m_aValues as $sKey=>$sValue)
{
if (stripos($sValue, $sContains) !== false)
{
$aRet[$sKey] = $sValue;
}
}
}
$this->SortValues($aRet);
return $aRet;
}
/**
* @param array $aValues Values to sort in the form keys => values
*
* @return void
* @since 3.1.0 N°1646 Create method
*/
public function SortValues(array &$aValues): void
{
// Sort alphabetically on values
natcasesort($aValues);
}
abstract protected function LoadValues($aArgs);
}
/**
* Set of existing values for an attribute, given a search filter
*
* @package iTopORM
*/
class ValueSetObjects extends ValueSetDefinition
{
protected $m_sContains;
protected $m_sOperation;
protected $m_sFilterExpr; // in OQL
protected $m_sValueAttCode;
protected $m_aOrderBy;
protected $m_oExtraCondition;
private $m_bAllowAllData;
private $m_aModifierProperties;
private $m_bSort;
private $m_iLimit;
/**
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
*/
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
{
$this->m_sContains = '';
$this->m_sOperation = '';
$this->m_sFilterExpr = $sFilterExp;
$this->m_sValueAttCode = $sValueAttCode;
$this->m_aOrderBy = $aOrderBy;
$this->m_bAllowAllData = $bAllowAllData;
$this->m_aModifierProperties = $aModifierProperties;
$this->m_oExtraCondition = null;
$this->m_bSort = true;
$this->m_iLimit = 0;
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
$this->m_bIsLoaded = false;
}
public function SetCondition(DBSearch $oFilter)
{
$this->m_oExtraCondition = $oFilter;
$this->m_bIsLoaded = false;
}
public function SetOrderBy(array $aOrderBy)
{
$this->m_aOrderBy = $aOrderBy;
}
public function ToObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
if ($this->m_bAllowAllData)
{
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
}
else
{
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
}
if (!is_null($this->m_oExtraCondition))
{
$oFilter = $oFilter->Intersect($this->m_oExtraCondition);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
foreach ($aProperties as $sProperty => $value)
{
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
if ($iAdditionalValue > 0)
{
$oSearchAdditionalValue = new DBObjectSearch($oFilter->GetClass());
$oSearchAdditionalValue->AddConditionExpression( new BinaryExpression(
new FieldExpression('id', $oSearchAdditionalValue->GetClassAlias()),
'=',
new VariableExpression('current_extkey_id'))
);
$oSearchAdditionalValue->AllowAllData();
$oSearchAdditionalValue->SetArchiveMode(true);
$oSearchAdditionalValue->SetInternalParams( array('current_extkey_id' => $iAdditionalValue) );
$oFilter = new DBUnionSearch(array($oFilter, $oSearchAdditionalValue));
}
return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
}
/**
* @inheritDoc
* @throws CoreException
* @throws OQLException
*/
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation))
{
$this->LoadValues($aArgs, $sContains, $sOperation);
$this->m_bIsLoaded = true;
}
// The results are already filtered and sorted (on friendly name)
$aRet = $this->m_aValues;
return $aRet;
}
/**
* @param $aArgs
* @param string $sContains
* @param string $sOperation 'contains' or 'equals_start_with'
*
* @return bool
* @throws \CoreException
* @throws \OQLException
*/
protected function LoadValues($aArgs, $sContains = '', $sOperation = 'contains')
{
$this->m_sContains = $sContains;
$this->m_sOperation = $sOperation;
$this->m_aValues = array();
$oFilter = $this->GetFilter($sOperation, $sContains);
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
if (empty($this->m_sValueAttCode)) {
$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
} else {
$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
}
$oObjects->OptimizeColumnLoad($aAttToLoad);
while ($oObject = $oObjects->Fetch()) {
if (empty($this->m_sValueAttCode)) {
$this->m_aValues[$oObject->GetKey()] = $oObject->GetName();
} else {
$this->m_aValues[$oObject->GetKey()] = $oObject->Get($this->m_sValueAttCode);
}
}
return true;
}
/**
* Get filter for functions LoadValues and LoadValuesForAutocomplete
*
* @param $sOperation
* @param $sContains
*
* @return \DBObjectSearch|\DBSearch|\DBUnionSearch|false|mixed
* @throws \CoreException
* @throws \OQLException
* @since 3.0.3 3.1.0
*/
protected function GetFilter($sOperation, $sContains)
{
$this->m_sContains = $sContains;
$this->m_sOperation = $sOperation;
if ($this->m_bAllowAllData) {
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
} else {
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
}
if (!$oFilter) {
return false;
}
if (!is_null($this->m_oExtraCondition)) {
$oFilter = $oFilter->Intersect($this->m_oExtraCondition);
}
foreach ($this->m_aModifierProperties as $sPluginClass => $aProperties) {
foreach ($aProperties as $sProperty => $value) {
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
$sClass = $oFilter->GetClass();
switch ($this->m_sOperation) {
case 'equals':
case 'start_with':
if ($this->m_sOperation === 'start_with') {
$this->m_sContains .= '%';
$sOperator = 'LIKE';
} else {
$sOperator = '=';
}
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
if (count($aAttributes) > 0) {
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($this->m_sContains);
foreach ($aAttributes as $sAttribute) {
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, $sOperator, $oValueExpr);
$oNewFilter->AddConditionExpression($oCondition);
$aFilters[] = $oNewFilter;
}
// Unions are much faster than OR conditions
$oFilter = new DBUnionSearch($aFilters);
} else {
$oValueExpr = new ScalarExpression($this->m_sContains);
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, $sOperator, $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
}
break;
default:
$oValueExpr = new ScalarExpression('%'.$this->m_sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
break;
}
return $oFilter;
}
public function GetValuesDescription()
{
return 'Filter: '.$this->m_sFilterExpr;
}
public function GetFilterExpression()
{
return $this->m_sFilterExpr;
}
/**
* @param $iLimit
*/
public function SetLimit($iLimit)
{
$this->m_iLimit = $iLimit;
}
/**
* @param $bSort
*/
public function SetSort($bSort)
{
$this->m_bSort = $bSort;
}
public function GetValuesForAutocomplete($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation))
{
$this->LoadValuesForAutocomplete($aArgs, $sContains, $sOperation);
$this->m_bIsLoaded = true;
}
// The results are already filtered and sorted (on friendly name)
$aRet = $this->m_aValues;
return $aRet;
}
/**
* @param $aArgs
* @param string $sContains
* @param string $sOperation 'contains' or 'equals_start_with'
*
* @return bool
* @throws \CoreException
* @throws \OQLException
*/
protected function LoadValuesForAutocomplete($aArgs, $sContains = '', $sOperation = 'contains')
{
$this->m_aValues = array();
$oFilter = $this->GetFilter($sOperation, $sContains);
$sClass = $oFilter->GetClass();
$sClassAlias = $oFilter->GetClassAlias();
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
if (empty($this->m_sValueAttCode)) {
$aAttToLoad = ['friendlyname'];
} else {
$aAttToLoad = [$this->m_sValueAttCode];
}
$sImageAttr = MetaModel::GetImageAttributeCode($sClass);
if (!empty($sImageAttr)) {
$aAttToLoad [] = $sImageAttr;
}
$aComplementAttributeSpec = MetaModel::GetNameSpec($sClass, FriendlyNameType::COMPLEMENTARY);
$sFormatAdditionalField = $aComplementAttributeSpec[0];
$aAdditionalField = $aComplementAttributeSpec[1];
if (count($aAdditionalField) > 0) {
if (is_array($aAdditionalField)) {
$aAttToLoad = array_merge($aAttToLoad, $aAdditionalField);
} else {
$aAttToLoad [] = $aAdditionalField;
}
}
$oObjects->OptimizeColumnLoad([$sClassAlias => $aAttToLoad]);
while ($oObject = $oObjects->Fetch()) {
$aData = [];
if (empty($this->m_sValueAttCode)) {
$aData['label'] = $oObject->GetName();
} else {
$aData['label'] = $oObject->Get($this->m_sValueAttCode);
}
if ($oObject->IsObsolete()) {
$aData['obsolescence_flag'] = '1';
} else {
$aData['obsolescence_flag'] = '0';
}
if (count($aAdditionalField) > 0) {
$aArguments = [];
foreach ($aAdditionalField as $sAdditionalField) {
array_push($aArguments, $oObject->Get($sAdditionalField));
}
$aData['additional_field'] = utils::VSprintf($sFormatAdditionalField, $aArguments);
} else {
$aData['additional_field'] = '';
}
if (!empty($sImageAttr)) {
/** @var \ormDocument $oImage */
$oImage = $oObject->Get($sImageAttr);
if (!$oImage->IsEmpty()) {
$aData['picture_url'] = $oImage->GetDisplayURL($sClass, $oObject->GetKey(), $sImageAttr);
$aData['initials'] = '';
} else {
$aData['initials'] = utils::ToAcronym($aData['label']);
}
}
$this->m_aValues[$oObject->GetKey()] = $aData;
}
return true;
}
}
/**
* Fixed set values (could be hardcoded in the business model)
*
* @package iTopORM
*/
class ValueSetEnum extends ValueSetDefinition
{
protected $m_values;
/**
* @var bool $bSortByValues If true, values will be sorted at runtime (on their values, not their keys), otherwise it is sorted at compile time in a predefined order.
* {@see \MFCompiler::CompileAttributeEnumValues()} for complete reasons.
* @since 3.1.0 N°1646
*/
protected bool $bSortByValues;
/**
* @param array|string $Values
* @param bool $bLocalizedSort
*
* @since 3.1.0 N°1646 Add $bLocalizedSort parameter
* @since 3.2.0 N°7157 $Values can be an array of backed-enum cases
*/
public function __construct($Values, bool $bSortByValues = false)
{
$this->m_values = $Values;
$this->bSortByValues = $bSortByValues;
}
/**
* @see \ValueSetEnum::$bSortByValues
* @return bool
* @since 3.1.0 N°1646
*/
public function IsSortedByValues(): bool
{
return $this->bSortByValues;
}
// Helper to export the data model
public function GetValueList()
{
$this->LoadValues(null);
return $this->m_aValues;
}
/**
* @inheritDoc
* @since 3.1.0 N°1646 Overload method
*/
public function SortValues(array &$aValues): void
{
// Force sort by values only if necessary
if ($this->bSortByValues) {
natcasesort($aValues);
return;
}
// Don't sort values as we rely on the order defined during compilation
return;
}
/**
* @param array|string $aArgs
*
* @return true
*/
protected function LoadValues($aArgs)
{
$aValues = [];
if (is_array($this->m_values))
{
foreach ($this->m_values as $key => $value) {
// Handle backed-enum case
if (is_object($value) && enum_exists(get_class($value))) {
$aValues[$value->value] = $value->value;
continue;
}
$aValues[$key] = $value;
}
}
elseif (is_string($this->m_values) && strlen($this->m_values) > 0)
{
foreach (explode(",", $this->m_values) as $sVal)
{
$sVal = trim($sVal);
$sKey = $sVal;
$aValues[$sKey] = $sVal;
}
}
else
{
$aValues = [];
}
$this->m_aValues = $aValues;
return true;
}
}
class ValueSetEnumPadded extends ValueSetEnum
{
/**
* @inheritDoc
* @since 3.1.0 N°6448 Add $bSortByValues parameter
*/
public function __construct($Values, bool $bSortByValues = false)
{
parent::__construct($Values, $bSortByValues);
if (is_string($Values))
{
$this->LoadValues(null);
}
else
{
$this->m_aValues = $Values;
}
$aPaddedValues = array();
foreach ($this->m_aValues as $sKey => $sVal)
{
// Pad keys to the min. length required by the \AttributeSet
$sKey = str_pad($sKey, 3, '_', STR_PAD_LEFT);
$aPaddedValues[$sKey] = $sVal;
}
$this->m_values = $aPaddedValues;
}
}
/**
* Fixed set values, defined as a range: 0..59 (with an optional increment)
*
* @package iTopORM
*/
class ValueSetRange extends ValueSetDefinition
{
protected $m_iStart;
protected $m_iEnd;
public function __construct($iStart, $iEnd, $iStep = 1)
{
$this->m_iStart = $iStart;
$this->m_iEnd = $iEnd;
$this->m_iStep = $iStep;
}
protected function LoadValues($aArgs)
{
$iValue = $this->m_iStart;
for($iValue = $this->m_iStart; $iValue <= $this->m_iEnd; $iValue += $this->m_iStep)
{
$this->m_aValues[$iValue] = $iValue;
}
return true;
}
}
/**
* Data model classes
*
* @package iTopORM
*/
class ValueSetEnumClasses extends ValueSetEnum
{
protected $m_sCategories;
public function __construct($sCategories = '', $sAdditionalValues = '')
{
$this->m_sCategories = $sCategories;
parent::__construct($sAdditionalValues, true /* Classes are always sorted alphabetically */);
}
protected function LoadValues($aArgs)
{
// Call the parent to parse the additional values...
parent::LoadValues($aArgs);
// Translate the labels of the additional values
foreach($this->m_aValues as $sClass => $void)
{
if (MetaModel::IsValidClass($sClass))
{
$this->m_aValues[$sClass] = MetaModel::GetName($sClass);
}
else
{
unset($this->m_aValues[$sClass]);
}
}
// Then, add the classes from the category definition
foreach (MetaModel::GetClasses($this->m_sCategories) as $sClass)
{
if (MetaModel::IsValidClass($sClass))
{
$this->m_aValues[$sClass] = MetaModel::GetName($sClass);
}
else
{
unset($this->m_aValues[$sClass]);
}
}
return true;
}
}

View File

@@ -39,6 +39,23 @@
//
// .site-nav a { color:#BADA55!important; }
$ibo-shame--switch--width: 36px !default;
$ibo-shame--switch--height: 20px !default;
$ibo-shame--slider--background-color: $ibo-color-secondary-600 !default;
$ibo-shame--slider--before--height: 15px !default;
$ibo-shame--slider--before--width: 15px !default;
$ibo-shame--slider--before--background-color: $ibo-color-secondary-300 !default;
$ibo-shame--slider--is-checked--background-color: $ibo-color-primary-600 !default;
$ibo-shame--slider--is-focus--box-shadow: 0 0 1px $ibo-color-primary-600 !default;
$ibo-shame--slider--is-round--border-radius: 20px !default;
$ibo-shame--slider--is-round--before--border-radius: 7px !default;
// N°2847 - Recolor svg illustrations with iTop's primary color
.ibo-svg-illustration--container > svg *[fill="#6c63ff"]{
fill: $ibo-svg-illustration--fill;
@@ -56,8 +73,8 @@
.switch {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
width: $ibo-shame--switch--width;
height: $ibo-shame--switch--height;
vertical-align: baseline;
}
@@ -74,27 +91,27 @@
left: 0;
right: 0;
bottom: 0;
background-color: $ibo-color-secondary-600;
background-color: $ibo-shame--slider--background-color;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 15px;
width: 15px;
height: $ibo-shame--slider--before--height;
width: $ibo-shame--slider--before--width;
left: 3px;
bottom: 3px;
background-color: $ibo-color-secondary-300;
background-color: $ibo-shame--slider--before--background-color;
transition: .4s;
}
input:checked + .slider {
background-color: $ibo-color-primary-600;
background-color: $ibo-shame--slider--is-checked--background-color;
}
input:focus + .slider {
box-shadow: 0 0 1px $ibo-color-primary-600;
box-shadow: $ibo-shame--slider--is-focus--box-shadow;
}
input:checked + .slider:before {
@@ -103,9 +120,9 @@ input:checked + .slider:before {
/* Rounded sliders */
.slider.round {
border-radius: 20px;
border-radius: $ibo-shame--slider--is-round--border-radius;
}
.slider.round:before {
border-radius: 7px;
border-radius: $ibo-shame--slider--is-round--before--border-radius;
}

View File

@@ -3,12 +3,14 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-bulk--bulk-modify--incompatible-attribute {
$ibo-bulk--bulk-modify--incompatible-attribute--color--margin-right: $ibo-vendors-selectize--item--icon--margin-right !default;
$ibo-bulk--bulk-modify--incompatible-attribute--color: $ibo-color-information-500 !default;
.ibo-bulk--bulk-modify--incompatible-attribute {
&:before{
margin-right: $ibo-vendors-selectize--item--icon--margin-right;
margin-right: $ibo-bulk--bulk-modify--incompatible-attribute--color--margin-right;
@extend %fa-solid-base;
content: '\f05a';
color: $ibo-color-information-500;
color: $ibo-bulk--bulk-modify--incompatible-attribute--color;
}
}

View File

@@ -3,12 +3,16 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-linked-set--bulk-tooltip-info--content: '\f05a' !default;
$ibo-linked-set--bulk-tooltip-info--color: $ibo-color-information-500 !default;
$ibo-linked-set--bulk-tooltip-info--margin-right: $ibo-vendors-selectize--item--icon--margin-right !default;
.ibo-linked-set--bulk-tooltip-info {
font-size: $ibo-font-size-100;
@extend %ibo-font-ral-nor-100;
&:before{
margin-right: $ibo-vendors-selectize--item--icon--margin-right;
margin-right: $ibo-linked-set--bulk-tooltip-info--margin-right;
@extend %fa-solid-base;
content: '\f05a';
color: $ibo-color-information-500;
content: $ibo-linked-set--bulk-tooltip-info--content;
color: $ibo-linked-set--bulk-tooltip-info--color;
}
}

View File

@@ -16,7 +16,7 @@
@import "panel/all";
@import "pill/all";
@import "dashlet/all";
@import "add-to-dashboard";
@import "prop-within-details";
@import "caselog-entry-form-within-activity-panel";
@import "tab-container-within-panel";
@import "medallion-with-blocklist";

View File

@@ -3,13 +3,17 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-medallion-with-blocklist--icon--image--margin-x: auto !default;
$ibo-medallion-with-blocklist--icon--image--margin-y: 0 !default;
$ibo-medallion-with-blocklist--icon--description--margin-top: $ibo-spacing-400 !default;
.ibo-block-list--medallion{
flex-direction: column;
align-items: center;
> .ibo-medallion-icon--image{
margin: 0 auto;
margin: $ibo-medallion-with-blocklist--icon--image--margin-y $ibo-medallion-with-blocklist--icon--image--margin-x;
~ .ibo-medallion-icon--description{
margin-top: 12px;
margin-top: $ibo-medallion-with-blocklist--icon--description--margin-top;
}
}
> .ibo-medallion-icon--description{

View File

@@ -9,8 +9,4 @@
.ibo-details .ibo-prop--apply {
display: table-column;
}
.ibo-details {
margin-top: 5px;
}

View File

@@ -15,6 +15,13 @@ $ibo-panel-with-tab-container--margin-bottom: -1 * $ibo-panel--body--padding-bot
$ibo-panel-with-tab-container--tab-toggler--font-size--is-sticking: $ibo-font-size-100 !default;
$ibo-tab-container-within-panel--tabs-list--padding-top: $ibo-spacing-800 !default;
// I'm not sure where these values come from, might need to be replaced by variables
$ibo-tab-container-within-panel--tabs-list--min-width: calc(32px + 90px + 32px) !default;
$ibo-tab-container-within-panel--tabs-header--height: $ibo-spacing-800 !default;
$ibo-tab-container-within-panel--tabs--list--is-sticking--z-index: 10 !default;
$ibo-tab-container-within-panel--tabs--list--is-sticking--is-not-vertical--padding-left: $ibo-spacing-0 !default;
// Note: We use the child ">" selector to ensure this applies only to the child tab container, not another one that would be nested
.ibo-panel {
> .ibo-panel--body {
@@ -35,15 +42,15 @@ $ibo-panel-with-tab-container--tab-toggler--font-size--is-sticking: $ibo-font-si
flex-direction: row;
> .ibo-tab-container--tabs-list {
padding-top: 50px;
padding-top: $ibo-tab-container-within-panel--tabs-list--padding-top;
flex-direction: column;
height: auto;
padding-left: unset;
margin-right: unset;
min-width: calc(32px + 90px + 32px);
min-width: $ibo-tab-container-within-panel--tabs-list--min-width;
> .ibo-tab-container--tab-header {
height: 50px;
height: $ibo-tab-container-within-panel--tabs-header--height;
width: 100%;
justify-content: left;
@@ -68,12 +75,12 @@ $ibo-panel-with-tab-container--tab-toggler--font-size--is-sticking: $ibo-font-si
> .ibo-tab-container {
> .ibo-tab-container--tabs-list.ibo-is-sticking {
position: fixed;
z-index: 10;
z-index: $ibo-tab-container-within-panel--tabs--list--is-sticking--z-index;
}
&:not(.ibo-is-vertical){
> .ibo-tab-container--tabs-list.ibo-is-sticking {
padding-left: 0;
padding-left: $ibo-tab-container-within-panel--tabs--list--is-sticking--is-not-vertical--padding-left;
.ibo-tab-container--tab-toggler,
.ibo-tab-container--extra-tabs-list-toggler {

View File

@@ -3,8 +3,11 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-datatable-within-panel--panel-body--width: 100% !default;
.ibo-panel .ibo-panel--body{
.ibo-datatable{
width: 100%
width: $ibo-datatable-within-panel--panel-body--width;
}
}

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