13 |
14 |
15 | {{ fields.dropdownArrayField(
16 | 'displayinfofor',
17 | item.fields['displayinfofor'],
18 | displayvalues,
19 | __('Enable timer on tasks', 'actualtime'),
20 | options|merge({'width': 'auto'})
21 | ) }}
22 |
23 | {{ fields.dropdownYesNo(
24 | 'showtimerpopup',
25 | item.fields['showtimerpopup'],
26 | __('Display pop-up window with current running timer', 'actualtime'),
27 | options
28 | ) }}
29 |
30 | {{ fields.dropdownYesNo(
31 | 'showtimerinbox',
32 | item.fields['showtimerinbox'],
33 | __('Display actual time in closed task box (\'Processing ticket\' list)', 'actualtime'),
34 | options
35 | ) }}
36 |
37 | {{ fields.dropdownYesNo(
38 | 'autoopenrunning',
39 | item.fields['autoopenrunning'],
40 | __('Automatically open task with timer running', 'actualtime'),
41 | options
42 | ) }}
43 |
44 | {{ fields.dropdownYesNo(
45 | 'autoupdate_duration',
46 | item.fields['autoupdate_duration'],
47 | __('Automatically update the duration', 'actualtime'),
48 | options
49 | ) }}
50 |
51 | {{ fields.dropdownYesNo(
52 | 'planned_task',
53 | item.fields['planned_task'],
54 | __('Enable Timer Only on Scheduled Task Day', 'actualtime'),
55 | options
56 | ) }}
57 |
58 | {{ fields.dropdownYesNo(
59 | 'multiple_day',
60 | item.fields['multiple_day'],
61 | __('Enable Timer Only on Task\'s Start Day', 'actualtime'),
62 | options
63 | ) }}
64 |
65 | {{ fields.numberField(
66 | 'task_limit',
67 | item.fields['task_limit'],
68 | __('Task limit in hours for ActualTime', 'actualtime'),
69 | options|merge({
70 | 'input_addclass': 'w-auto',
71 | 'min': 0,
72 | 'max': 24,
73 | 'step': 1
74 | })
75 | ) }}
76 |
77 |
78 |
79 |
80 | {{ include('components/form/buttons.html.twig') }}
--------------------------------------------------------------------------------
/tools/phpstan/rules/InputValidationRule.php:
--------------------------------------------------------------------------------
1 | getFile();
42 |
43 | // Skip files in "ajax" or "front" directories
44 | if (strpos($filePath, '/ajax/') !== false || strpos($filePath, '/front/') !== false) {
45 | return []; // Do not apply the rule to these files
46 | }
47 |
48 | // If file is apirest.class.php, skip the rule
49 | if (strpos($filePath, '/apirest.class.php') !== false) {
50 | return []; // Do not apply the rule to this file
51 | }
52 |
53 | // Get the function name
54 | $functionName = $scope->getFunctionName();
55 |
56 | // Allowed functions
57 | if (in_array($functionName, ['prepareInputForAdd', 'prepareInputForUpdate'], true)) {
58 | return []; // Do not apply the rule to these functions
59 | }
60 |
61 | if (is_string($node->name) && in_array($node->name, self::SUPERGLOBALS, true)) {
62 | // Check if the superglobal is being accessed with a specific key
63 | // If not, allow assignment Ex. $get = $_GET;
64 | if ($node->getAttribute('key') === null) {
65 | return [];
66 | }
67 |
68 | return [
69 | RuleErrorBuilder::message(
70 | sprintf(
71 | 'The use of the superglobal "%s" without validation is discouraged out of ajax and front files.',
72 | print_r($node->name, true),
73 | ),
74 | )->build(),
75 | ];
76 | }
77 |
78 | return [];
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tools/phpstan/rules/SensitiveInfoExposureRule.php:
--------------------------------------------------------------------------------
1 | name instanceof Node\Name) {
37 | $functionName = (string) $node->name;
38 | if (in_array($functionName, self::SENSITIVE_FUNCTIONS, true)) {
39 | // Special handling for print_r
40 | if ($functionName === 'print_r') {
41 | // Check if the second argument is set and is `true`
42 | if (isset($node->args[1])) {
43 | $secondArg = $node->args[1]->value;
44 |
45 | // Allow only if the second argument is a literal `true`
46 | if ($secondArg->name->toLowerString() === 'true') {
47 | return []; // No error if print_r($var, true)
48 | }
49 | }
50 |
51 | // If no second argument or it's not `true`, report an error
52 | return [
53 | RuleErrorBuilder::message(
54 | 'The use of "print_r" without the second argument as `true` may expose sensitive information and should be avoided in production environments.',
55 | )
56 | ->tip('Use "print_r($var, true)" to avoid direct output or consider removing this call.')
57 | ->build(),
58 | ];
59 | }
60 |
61 | return [
62 | RuleErrorBuilder::message(
63 | sprintf(
64 | 'The use of the function "%s" may expose sensitive information and should be avoided in production environments.',
65 | $functionName,
66 | ),
67 | )->build(),
68 | ];
69 | }
70 | }
71 |
72 | return [];
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tools/phpstan/rules/UnsafeFunctionsRule.php:
--------------------------------------------------------------------------------
1 | build(),
55 | ];
56 | }
57 |
58 | // Handle other unsafe functions
59 | if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) {
60 | $functionName = (string) $node->name;
61 |
62 | if (in_array($functionName, self::UNSAFE_FUNCTIONS, true)) {
63 | return [
64 | RuleErrorBuilder::message(
65 | sprintf(
66 | 'The use of the unsafe function "%s" is prohibited for security reasons.',
67 | $functionName,
68 | ),
69 | )->build(),
70 | ];
71 | }
72 |
73 | // Check for unsafe cryptographic functions
74 | if (in_array($functionName, self::UNSAFE_CRYPTO_FUNCTIONS, true)) {
75 | return [
76 | RuleErrorBuilder::message(
77 | sprintf(
78 | 'The use of the insecure cryptographic function "%s" is discouraged. Use modern alternatives like "password_hash()" or "hash(\'sha256\', ...)" instead.',
79 | $functionName,
80 | ),
81 | )->build(),
82 | ];
83 | }
84 | }
85 |
86 | return [];
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tools/phpstan/rules/SqlInjectionRule.php:
--------------------------------------------------------------------------------
1 | expr instanceof Concat) {
34 | $left = $node->expr->left;
35 | $right = $node->expr->right;
36 |
37 | // Skip if neither side is a string literal
38 | if (!($left instanceof String_) && !($right instanceof String_)) {
39 | return [];
40 | }
41 |
42 | $variableName = $node->var->name ?? null;
43 | $sqlKeywords = ['sql', 'query', 'statement', 'db'];
44 | if (
45 | is_string($variableName)
46 | && preg_match('/(' . implode('|', $sqlKeywords) . ')/i', $variableName) === 0
47 | ) {
48 | return []; // Skip if the variable name doesn't suggest SQL
49 | }
50 |
51 | // Check if the left or right side of the concatenation is a string containing SQL keywords
52 | if (
53 | ($left instanceof String_ && $this->containsSqlKeywords($left->value)) ||
54 | ($right instanceof String_ && $this->containsSqlKeywords($right->value))
55 | ) {
56 | return [
57 | RuleErrorBuilder::message(
58 | 'Detected a SQL query constructed with string concatenation, which may lead to SQL injection vulnerabilities. Use prepared statements instead.',
59 | )->build(),
60 | ];
61 | }
62 | }
63 |
64 | return [];
65 | }
66 |
67 | /**
68 | * Checks if a string contains common SQL keywords.
69 | *
70 | * @param string $value
71 | * @return bool
72 | */
73 | private function containsSqlKeywords(string $value): bool
74 | {
75 | $keywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'WHERE', 'FROM', 'JOIN', 'DROP', 'ALTER'];
76 | foreach ($keywords as $keyword) {
77 | if (stripos($value, $keyword) !== false) {
78 | return true;
79 | }
80 | }
81 | return false;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tools/extract_template.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
4 | PARENT_FOLDER_PATH=$(dirname "$SCRIPT_DIR")
5 | PLUGINNAME=$(basename "$PARENT_FOLDER_PATH")
6 |
7 | POTFILE=$PLUGINNAME.pot
8 | LOCALES=$PARENT_FOLDER_PATH/locales
9 |
10 | # check if xgettext is installed
11 | if ! command -v xgettext &>/dev/null; then
12 | echo "Error: xgettext is not installed"
13 | exit 1
14 | fi
15 |
16 | # if no exist locales folder, create it
17 | if [ ! -d "$LOCALES" ]; then
18 | mkdir $LOCALES
19 | fi
20 |
21 | INIT_PWD=$PWD
22 | if [ ! "$PARENT_FOLDER_PATH" = "$INIT_PWD" ]; then
23 | cd $PARENT_FOLDER_PATH
24 | fi
25 |
26 | # Clean existing file
27 | rm -f $LOCALES/$POTFILE && touch $LOCALES/$POTFILE >/dev/null
28 |
29 | echo Searching PHP files...
30 | # Append locales from PHP
31 | xgettext $(find -type f -name "*.php") -o $LOCALES/$POTFILE -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po --join-existing \
32 | --keyword=__:1,2t -d $PLUGINNAME --copyright-holder "TICgal" >/dev/null 2>&1
33 |
34 | echo Searching JS files...
35 | # Append locales from JavaScript
36 | xgettext $(find -type f -name "*.js") -o $LOCALES/$POTFILE -L JavaScript --add-comments=TRANS --from-code=UTF-8 --force-po --join-existing \
37 | --keyword=__:1,2t -d $PLUGINNAME --copyright-holder "TICgal" >/dev/null 2>&1
38 |
39 | echo Searching TWIG files...
40 | # Append locales from Twig templates
41 | for file in $(find ./templates -type f -name "*.twig"); do
42 | # 1. Convert file content to replace "{{ function(.*) }}" by "" and extract strings via std input
43 | # 2. Replace "standard input:line_no" by file location in po file comments
44 | contents=$(cat $file | sed -r "s|\{\{\s*([a-z0-9_]+\(.*\))\s*\}\}||gi")
45 | cat $file | perl -0pe "s/\{\{(.*?)\}\}//gism" | xgettext - -o $LOCALES/$POTFILE -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po --join-existing \
46 | --keyword=__:1,2t -d $PLUGINNAME --copyright-holder "TICgal"
47 | sed -i -r "s|standard input:([0-9]+)|$(echo $file | sed "s|./||"):\1|g" $LOCALES/$POTFILE
48 | done
49 |
50 | #Update main language
51 | LANG=C msginit --no-translator -i $LOCALES/$POTFILE -l en_GB -o $LOCALES/en_GB.po
52 |
53 | ### for using tx :
54 | ##tx set --execute --auto-local -r GLPI.glpipot 'locales/