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
This commit is contained in:
Benjamin Dalsass
2025-09-10 07:57:41 +02:00
242 changed files with 4558 additions and 9999 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

View File

@@ -1,104 +0,0 @@
# iTop version history
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'themeVariables': {
'git0': 'lawngreen',
'git3': 'dodgerblue',
'git4': 'grey',
'git5': 'grey',
'git6': 'grey',
'git7': 'grey'
}, 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
gitGraph
commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT
branch support/2.3 order: 900
commit id: "2016-07-08" tag: "2.3.1"
commit id: "2016-12-22" tag: "2.3.3"
commit id: "2017-04-14" tag: "2.3.4"
checkout develop
commit id: "2017-07-12" tag: "2.4.0-beta" type: REVERSE
commit id: "2017-11-16" tag: "2.4.0" type: HIGHLIGHT
branch support/2.4 order: 890
commit id: "2018-02-14" tag: "2.4.1"
checkout develop
commit id: "2018-04-25" tag: "2.5.0-beta" type: REVERSE
checkout support/2.4
commit id: "2018-06-14" tag: "2.4.2"
checkout develop
commit id: "2018-06-27" tag: "2.5.0" type: HIGHLIGHT
branch support/2.5 order: 880
checkout develop
commit id: "2019-01-09" tag: "2.6.0" type: HIGHLIGHT
branch support/2.6 order: 870
commit id: "2019-03-28" tag: "2.6.1"
checkout develop
commit id: "2019-12-18" tag: "2.7.0-beta" type: REVERSE
checkout support/2.5
commit id: "2020-01-22" tag: "2.5.4"
checkout support/2.6
commit id: "2020-01-23" tag: "2.6.3"
checkout develop
commit id: "2020-01-29" tag: "2.7.0-beta2" type: REVERSE
commit id: "2020-04-01" tag: "2.7.0-1" type: HIGHLIGHT
checkout support/2.6
commit id: "2020-04-22" tag: "2.6.4"
checkout develop
branch support/2.7 order: 860
commit id: "2020-06-26" tag: "2.7.1"
checkout support/2.7
commit id: "2020-12-09" tag: "2.7.3"
commit id: "2021-03-31" tag: "2.7.4"
checkout develop
commit id: "2021-04-06" tag: "3.0.0-beta" type: REVERSE
checkout support/2.7
commit id: "2021-07-05" tag: "2.7.5"
checkout develop
commit id: "2021-07-05." tag: "3.0.0-beta2" type: REVERSE
checkout support/2.7
commit id: "2021-12-17" tag: "2.7.6"
checkout develop
commit id: "2022-01-04" tag: "3.0.0" type: HIGHLIGHT
branch support/3.0 order: 850
commit id: "2022-04-08" tag: "3.0.1"
checkout support/2.7
commit id: "2022-07-11" tag: "2.7.7"
checkout support/3.0
commit id: "2022-09-12" tag: "3.0.2-1"
checkout develop
checkout support/2.7
commit id: "2022-12-28" tag: "2.7.8"
checkout support/3.0
commit id: "2023-04-12" tag: "3.0.3"
checkout develop
commit id: "2023-06-19" tag: "3.1.0-beta" type: REVERSE
commit id: "2023-07-26" tag: "3.1.0-1" type: HIGHLIGHT
branch support/3.1 order: 840
checkout support/3.1
commit id: "2023-08-09" tag: "3.1.0-2"
checkout support/2.7
commit id: "2023-08-10" tag: "2.7.9"
checkout support/3.1
commit id: "2023-12-20" tag: "3.1.1"
checkout develop
commit id: "2024-01-15" tag: "Start 3.2" type: HIGHLIGHT
branch support/3.2 order: 830
checkout support/2.7
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"
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"
```
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

@@ -22,6 +22,7 @@ use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSet;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Spinner\SpinnerUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\UIBlock;
@@ -409,6 +410,9 @@ JS
$sEnvironment = addslashes(utils::GetCurrentEnvironment());
$oModalSpinner = SpinnerUIBlockFactory::MakeMedium(null, $sPleaseWaitBackup);
$sModalSpinnerHtml = BlockRenderer::RenderBlockTemplates($oModalSpinner);
$oP->add_script(
<<<JS
function LaunchBackupNow()
@@ -420,7 +424,7 @@ function LaunchBackupNow()
{
const oModal = CombodoModal.OpenModal({
title: '$sBackUpNow',
content: '<i class="ajax-spin fas fa-sync-alt fa-spin"></i> $sPleaseWaitBackup'
content: `$sModalSpinnerHtml`
});
var oParams = {};

View File

@@ -22,7 +22,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:KnownError/Attribute:problem_id+' => '',
'Class:KnownError/Attribute:problem_ref' => 'Rérérence problème lié',
'Class:KnownError/Attribute:problem_ref+' => '',
'Class:KnownError/Attribute:symptom' => 'Symptome',
'Class:KnownError/Attribute:symptom' => 'Symptôme',
'Class:KnownError/Attribute:symptom+' => '',
'Class:KnownError/Attribute:root_cause' => 'Cause première',
'Class:KnownError/Attribute:root_cause+' => '',

View File

@@ -122,6 +122,9 @@ $(function()
_create: function () {
this.element.addClass('ibo-activity-panel');
// Should be initialized globally, but as we don't actually do it
moment.locale(GetUserLanguage());
this._bindEvents();
// Lock

View File

@@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';

View File

@@ -11,7 +11,6 @@ return array(
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
'89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php',
'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php',
'c7baa00073ee9c61edf148c51917cfb4' => $vendorDir . '/twig/twig/src/Resources/escaper.php',

View File

@@ -36,8 +36,7 @@ if ($issues) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
);
}

View File

@@ -0,0 +1,132 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Traits\Relay;
if (version_compare(phpversion('relay'), '0.9.0', '>=')) {
/**
* @internal
*/
trait FtTrait
{
public function ftAggregate($index, $query, $options = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftAggregate(...\func_get_args());
}
public function ftAliasAdd($index, $alias): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftAliasAdd(...\func_get_args());
}
public function ftAliasDel($alias): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftAliasDel(...\func_get_args());
}
public function ftAliasUpdate($index, $alias): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftAliasUpdate(...\func_get_args());
}
public function ftAlter($index, $schema, $skipinitialscan = false): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftAlter(...\func_get_args());
}
public function ftConfig($operation, $option, $value = null): \Relay\Relay|array|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftConfig(...\func_get_args());
}
public function ftCreate($index, $schema, $options = null): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftCreate(...\func_get_args());
}
public function ftCursor($operation, $index, $cursor, $options = null): \Relay\Relay|array|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftCursor(...\func_get_args());
}
public function ftDictAdd($dict, $term, ...$other_terms): \Relay\Relay|false|int
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftDictAdd(...\func_get_args());
}
public function ftDictDel($dict, $term, ...$other_terms): \Relay\Relay|false|int
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftDictDel(...\func_get_args());
}
public function ftDictDump($dict): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftDictDump(...\func_get_args());
}
public function ftDropIndex($index, $dd = false): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftDropIndex(...\func_get_args());
}
public function ftExplain($index, $query, $dialect = 0): \Relay\Relay|false|string
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftExplain(...\func_get_args());
}
public function ftExplainCli($index, $query, $dialect = 0): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftExplainCli(...\func_get_args());
}
public function ftInfo($index): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftInfo(...\func_get_args());
}
public function ftProfile($index, $command, $query, $limited = false): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftProfile(...\func_get_args());
}
public function ftSearch($index, $query, $options = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftSearch(...\func_get_args());
}
public function ftSpellCheck($index, $query, $options = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftSpellCheck(...\func_get_args());
}
public function ftSynDump($index): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftSynDump(...\func_get_args());
}
public function ftSynUpdate($index, $synonym, $term_or_terms, $skipinitialscan = false): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftSynUpdate(...\func_get_args());
}
public function ftTagVals($index, $tag): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ftTagVals(...\func_get_args());
}
}
} else {
/**
* @internal
*/
trait FtTrait
{
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Traits\Relay;
if (version_compare(phpversion('relay'), '0.10.1', '>=')) {
/**
* @internal
*/
trait GetWithMetaTrait
{
public function getWithMeta($key): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->getWithMeta(...\func_get_args());
}
}
} else {
/**
* @internal
*/
trait GetWithMetaTrait
{
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Traits\Relay;
if (version_compare(phpversion('relay'), '0.11.1', '>=')) {
/**
* @internal
*/
trait IsTrackedTrait
{
public function isTracked($key): bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->isTracked(...\func_get_args());
}
}
} else {
/**
* @internal
*/
trait IsTrackedTrait
{
}
}

View File

@@ -0,0 +1,132 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Traits\Relay;
if (version_compare(phpversion('relay'), '0.11.0', '>=')) {
/**
* @internal
*/
trait Relay11Trait
{
public function cmsIncrBy($key, $field, $value, ...$fields_and_falues): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cmsIncrBy(...\func_get_args());
}
public function cmsInfo($key): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cmsInfo(...\func_get_args());
}
public function cmsInitByDim($key, $width, $depth): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cmsInitByDim(...\func_get_args());
}
public function cmsInitByProb($key, $error, $probability): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cmsInitByProb(...\func_get_args());
}
public function cmsMerge($dstkey, $keys, $weights = []): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cmsMerge(...\func_get_args());
}
public function cmsQuery($key, ...$fields): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->cmsQuery(...\func_get_args());
}
public function commandlog($subcmd, ...$args): \Relay\Relay|array|bool|int
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->commandlog(...\func_get_args());
}
public function hexpire($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexpire(...\func_get_args());
}
public function hexpireat($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexpireat(...\func_get_args());
}
public function hexpiretime($hash, $fields): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hexpiretime(...\func_get_args());
}
public function hgetdel($key, $fields): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetdel(...\func_get_args());
}
public function hgetex($hash, $fields, $expiry = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hgetex(...\func_get_args());
}
public function hpersist($hash, $fields): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hpersist(...\func_get_args());
}
public function hpexpire($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hpexpire(...\func_get_args());
}
public function hpexpireat($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hpexpireat(...\func_get_args());
}
public function hpexpiretime($hash, $fields): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hpexpiretime(...\func_get_args());
}
public function hpttl($hash, $fields): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hpttl(...\func_get_args());
}
public function hsetex($key, $fields, $expiry = null): \Relay\Relay|false|int
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->hsetex(...\func_get_args());
}
public function httl($hash, $fields): \Relay\Relay|array|false
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->httl(...\func_get_args());
}
public function serverName(): false|string
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->serverName(...\func_get_args());
}
public function serverVersion(): false|string
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->serverVersion(...\func_get_args());
}
}
} else {
/**
* @internal
*/
trait Relay11Trait
{
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Traits\Relay;
if (version_compare(phpversion('relay'), '0.9.0', '>=')) {
/**
* @internal
*/
trait SwapdbTrait
{
public function swapdb($index1, $index2): \Relay\Relay|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->swapdb(...\func_get_args());
}
}
} else {
/**
* @internal
*/
trait SwapdbTrait
{
}
}

View File

@@ -1,19 +0,0 @@
Copyright (c) 2021-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,37 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Php81;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Php81
{
public static function array_is_list(array $array): bool
{
if ([] === $array || $array === array_values($array)) {
return true;
}
$nextKey = -1;
foreach ($array as $k => $v) {
if ($k !== ++$nextKey) {
return false;
}
}
return true;
}
}

View File

@@ -1,18 +0,0 @@
Symfony Polyfill / Php81
========================
This component provides features added to PHP 8.1 core:
- [`array_is_list`](https://php.net/array_is_list)
- [`enum_exists`](https://php.net/enum-exists)
- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant
- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types)
- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used)
More information can be found in the
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
License
=======
This library is released under the [MIT license](LICENSE).

View File

@@ -1,51 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) {
/**
* @property string $data
*/
class CURLStringFile extends CURLFile
{
private $data;
public function __construct(string $data, string $postname, string $mime = 'application/octet-stream')
{
$this->data = $data;
parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname);
}
public function __set(string $name, $value): void
{
if ('data' !== $name) {
$this->$name = $value;
return;
}
if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) {
throw new TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string');
}
$this->name = 'data://application/octet-stream;base64,'.base64_encode($value);
}
public function __isset(string $name): bool
{
return isset($this->$name);
}
public function &__get(string $name)
{
return $this->$name;
}
}
}

View File

@@ -1,20 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (\PHP_VERSION_ID < 80100) {
#[Attribute(Attribute::TARGET_METHOD)]
final class ReturnTypeWillChange
{
public function __construct()
{
}
}
}

View File

@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php81 as p;
if (\PHP_VERSION_ID >= 80100) {
return;
}
if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) {
define('MYSQLI_REFRESH_REPLICA', 64);
}
if (!function_exists('array_is_list')) {
function array_is_list(array $array): bool { return p\Php81::array_is_list($array); }
}
if (!function_exists('enum_exists')) {
function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; }
}

View File

@@ -1,33 +0,0 @@
{
"name": "symfony/polyfill-php81",
"type": "library",
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"keywords": ["polyfill", "shim", "compatibility", "portable"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2"
},
"autoload": {
"psr-4": { "Symfony\\Polyfill\\Php81\\": "" },
"files": [ "bootstrap.php" ],
"classmap": [ "Resources/stubs" ]
},
"minimum-stability": "dev",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
}
}

View File

@@ -125,14 +125,14 @@ class GenericRuntime implements RuntimeInterface
}
if (!\is_callable($application)) {
throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
throw new \LogicException(\sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
}
$application = $application(...);
}
if ($_SERVER[$this->options['debug_var_name']] && ($r = new \ReflectionFunction($application)) && $r->getNumberOfRequiredParameters()) {
throw new \ArgumentCountError(sprintf('Zero argument should be required by the runner callable, but at least one is in "%s" on line "%d.', $r->getFileName(), $r->getStartLine()));
throw new \ArgumentCountError(\sprintf('Zero argument should be required by the runner callable, but at least one is in "%s" on line "%d.', $r->getFileName(), $r->getStartLine()));
}
return new ClosureRunner($application);
@@ -171,7 +171,7 @@ class GenericRuntime implements RuntimeInterface
if (!$runtime = $this->getRuntime($type)) {
$r = $parameter->getDeclaringFunction();
throw new \InvalidArgumentException(sprintf('Cannot resolve argument "%s $%s" in "%s" on line "%d": "%s" supports only arguments "array $context", "array $argv" and "array $request", or a runtime named "Symfony\Runtime\%1$sRuntime".', $type, $parameter->name, $r->getFileName(), $r->getStartLine(), get_debug_type($this)));
throw new \InvalidArgumentException(\sprintf('Cannot resolve argument "%s $%s" in "%s" on line "%d": "%s" supports only arguments "array $context", "array $argv" and "array $request", or a runtime named "Symfony\Runtime\%1$sRuntime".', $type, $parameter->name, $r->getFileName(), $r->getStartLine(), get_debug_type($this)));
}
return $runtime->getArgument($parameter, $type);

View File

@@ -70,7 +70,7 @@ class ComposerPlugin implements PluginInterface, EventSubscriberInterface
}
if (!is_file($autoloadTemplate)) {
throw new \InvalidArgumentException(sprintf('File "%s" defined under "extra.runtime.autoload_template" in your composer.json file not found.', $this->composer->getPackage()->getExtra()['runtime']['autoload_template']));
throw new \InvalidArgumentException(\sprintf('File "%s" defined under "extra.runtime.autoload_template" in your composer.json file not found.', $this->composer->getPackage()->getExtra()['runtime']['autoload_template']));
}
}

View File

@@ -28,7 +28,7 @@ class DebugClosureResolver extends ClosureResolver
$r = new \ReflectionFunction($closure);
throw new \TypeError(sprintf('Unexpected value of type "%s" returned, "object" expected from "%s" on line "%d".', get_debug_type($app), $r->getFileName(), $r->getStartLine()));
throw new \TypeError(\sprintf('Unexpected value of type "%s" returned, "object" expected from "%s" on line "%d".', get_debug_type($app), $r->getFileName(), $r->getStartLine()));
},
$arguments,
];

View File

@@ -36,7 +36,7 @@ class ClosureRunner implements RunnerInterface
if (null !== $exitStatus && !\is_int($exitStatus)) {
$r = new \ReflectionFunction($this->closure);
throw new \TypeError(sprintf('Unexpected value of type "%s" returned, "string|int|null" expected from "%s" on line "%d".', get_debug_type($exitStatus), $r->getFileName(), $r->getStartLine()));
throw new \TypeError(\sprintf('Unexpected value of type "%s" returned, "string|int|null" expected from "%s" on line "%d".', get_debug_type($exitStatus), $r->getFileName(), $r->getStartLine()));
}
return $exitStatus ?? 0;

View File

@@ -1,3 +1,67 @@
6.10.0 (2025-05-27)
- Embedded files support (Factur-X 1.07 / ZUGFeRD 2.3) #789
6.9.5 (2025-05-27)
- Automatically add destinations from HTML code #804
- Wrong default value when $table_el['old_cell_padding'] is missing #807
- Fixed PHP warning when empty hash link for image exists in HTML #809
- Fix for application of alpha component to SVG RGBA fills #810
6.9.4 (2025-05-13)
- Update donation link.
6.9.3 (2025-04-20)
- New fix for "Deserialization of untrusted data" (check on valid protocols).
- Removed global phar configuration.
6.9.2 (2025-04-18)
- Quick fix for "Deserialization of untrusted data" security vulnerability reported by Positive Technologies.
- Disable phar protocol globally.
6.9.1 (2025-04-03)
- Fixed "Path Traversal" security vulnerability reported by Positive Technologies.
6.9.0 (2025-03-30)
- Added PHP 8.4 testing.
- Removed tcpdf_import.php and tcpdf_parser.php files (for a parser check the tc-lib-pdf-parser project instead).
- Fix composer.json.
6.8.2 (2025-01-26)
- Fix some annotation flags values.
- Remove examples from packaging.
6.8.1 (2025-01-26) - UNTAGGED
- Check relative paths on SVG images.
6.8.0 (2024-12-23)
- Requires PHP 7.1+ and curl extension.
- Escape error message.
- Use strict time-constant function to compare TCPDF-tag hashes.
- Add K_CURLOPTS config array to set custom cURL options (NOTE: some defaults have changed).
- Add some addTTFfont fixes from tc-lib-pdf-font.
6.7.8 (2024-12-13)
- Improve SVG detection by checking for (mandatory) namespace.
- Use late state binding now that minimum PHP version is 5.5.
6.7.7 (2024-10-26)
- Update regular expression to avoid ReDoS (CVE-2024-22641)
- [PHP 8.4] Fix: Curl CURLOPT_BINARYTRANSFER deprecated #675
- SVG detection fix for inline data images #646
- Fix count svg #647
- Since the version 6.7.4, the "0" is considered like empty string and not displayed
- Fixed handling of transparency in PDF/A mode in addExtGState method
- Encrypt /DA string when document is encrypted
- Improve quality of generated seed, avoid potential security pitfall
- Try to use random_bytes() first if it's available
- Do not include the server parameters in the generated seed, as they might contain sensitive data
- Fix bug on _getannotsrefs when there are empty signature appearances but not other annot on a page
- Fix SVG coordinate parser that caused drawing artifacts
- Remove usage of xml_set_object() function
6.7.6 (2024-10-06)
- Forbid access to parent folder in HTML images.
6.7.5 (2024-04-20)
- Update GitHub actions
- fix: CSV-2024-22640 (#712)

View File

@@ -7,7 +7,7 @@
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
2002-2024 Nicola Asuni - Tecnick.com LTD
2002-2025 Nicola Asuni - Tecnick.com LTD
**********************************************************************
**********************************************************************

View File

@@ -1,12 +1,12 @@
# TCPDF
*PHP PDF Library*
[![Donate via PayPal](https://img.shields.io/badge/donate-paypal-87ceeb.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&currency_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20TCPDF%20project)
*Please consider supporting this project by making a donation via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&currency_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20TCPDF%20project)*
[![Donate via PayPal](https://img.shields.io/badge/donate-paypal-87ceeb.svg)](https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ)
*Please consider supporting this project by making a donation via [PayPal](https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ)*
* **category** Library
* **author** Nicola Asuni <info@tecnick.com>
* **copyright** 2002-2024 Nicola Asuni - Tecnick.com LTD
* **copyright** 2002-2025 Nicola Asuni - Tecnick.com LTD
* **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
* **link** http://www.tcpdf.org
* **source** https://github.com/tecnickcom/TCPDF

View File

@@ -1 +1 @@
6.7.5
6.10.0

View File

@@ -12,7 +12,7 @@
"barcodes"
],
"homepage": "http://www.tcpdf.org/",
"version": "6.7.5",
"version": "6.10.0",
"license": "LGPL-3.0-or-later",
"authors": [
{
@@ -22,15 +22,14 @@
}
],
"require": {
"php": ">=5.5.0"
"php": ">=7.1.0",
"ext-curl": "*"
},
"autoload": {
"classmap": [
"config",
"include",
"tcpdf.php",
"tcpdf_parser.php",
"tcpdf_import.php",
"tcpdf_barcodes_1d.php",
"tcpdf_barcodes_2d.php",
"include/tcpdf_colors.php",
@@ -43,10 +42,5 @@
"include/barcodes/pdf417.php",
"include/barcodes/qrcode.php"
]
},
"archive": {
"exclude": [
"/examples"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -275,7 +275,7 @@ class TCPDF_COLORS {
$color = strtolower($color);
// check for javascript color array syntax
if (strpos($color, '[') !== false) {
if (preg_match('/[\[][\"\'](t|g|rgb|cmyk)[\"\'][\,]?([0-9\.]*+)[\,]?([0-9\.]*+)[\,]?([0-9\.]*+)[\,]?([0-9\.]*+)[\]]/', $color, $m) > 0) {
if (preg_match('/[\[][\"\'](t|g|rgba|rgb|cmyk)[\"\'][\,]?([0-9\.]*+)[\,]?([0-9\.]*+)[\,]?([0-9\.]*+)[\,]?([0-9\.]*+)[\]]/', $color, $m) > 0) {
$returncolor = array();
switch ($m[1]) {
case 'cmyk': {
@@ -286,7 +286,8 @@ class TCPDF_COLORS {
$returncolor['K'] = max(0, min(100, (floatval($m[5]) * 100)));
break;
}
case 'rgb': {
case 'rgb':
case 'rgba': {
// RGB
$returncolor['R'] = max(0, min(255, (floatval($m[2]) * 255)));
$returncolor['G'] = max(0, min(255, (floatval($m[3]) * 255)));
@@ -317,6 +318,25 @@ class TCPDF_COLORS {
if (strlen($color) == 0) {
return $defcol;
}
// RGBA ARRAY
if (substr($color, 0, 4) == 'rgba') {
$codes = substr($color, 5);
$codes = str_replace(')', '', $codes);
$returncolor = explode(',', $codes);
// remove alpha component
array_pop($returncolor);
foreach ($returncolor as $key => $val) {
if (strpos($val, '%') > 0) {
// percentage
$returncolor[$key] = (255 * intval($val) / 100);
} else {
$returncolor[$key] = intval($val); /* floatize */
}
// normalize value
$returncolor[$key] = max(0, min(255, $returncolor[$key]));
}
return $returncolor;
}
// RGB ARRAY
if (substr($color, 0, 3) == 'rgb') {
$codes = substr($color, 4);

View File

@@ -1,13 +1,13 @@
<?php
//============================================================+
// File name : tcpdf_fonts.php
// Version : 1.1.0
// Version : 1.1.1
// Begin : 2008-01-01
// Last Update : 2014-12-10
// Last Update : 2024-12-23
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2008-2014 Nicola Asuni - Tecnick.com LTD
// Copyright (C) 2008-2025 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
@@ -42,7 +42,7 @@
* @class TCPDF_FONTS
* Font methods for TCPDF library.
* @package com.tecnick.tcpdf
* @version 1.1.0
* @version 1.1.1
* @author Nicola Asuni - info@tecnick.com
*/
class TCPDF_FONTS {
@@ -191,29 +191,30 @@ class TCPDF_FONTS {
fclose($fp);
// get font info
$fmetric['Flags'] = $flags;
preg_match ('#/FullName[\s]*\(([^\)]*)#', $font, $matches);
preg_match ('#/FullName[\s]*+\(([^\)]*+)#', $font, $matches);
$fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches);
$fmetric['bbox'] = trim($matches[1]);
$bv = explode(' ', $fmetric['bbox']);
$fmetric['Ascent'] = intval($bv[3]);
$fmetric['Descent'] = intval($bv[1]);
preg_match('#/ItalicAngle[\s]*([0-9\+\-]*)#', $font, $matches);
preg_match('#/FontBBox[\s]*+{([^}]*+)#', $font, $matches);
$rawbvl = explode(' ', trim($matches[1]));
$bvl = [(int) $rawbvl[0], (int) $rawbvl[1], (int) $rawbvl[2], (int) $rawbvl[3]];
$fmetric['bbox'] = implode(' ', $bvl);
$fmetric['Ascent'] = $bvl[3];
$fmetric['Descent'] = $bvl[1];
preg_match('#/ItalicAngle[\s]*+([0-9\+\-]*+)#', $font, $matches);
$fmetric['italicAngle'] = intval($matches[1]);
if ($fmetric['italicAngle'] != 0) {
$fmetric['Flags'] |= 64;
}
preg_match('#/UnderlinePosition[\s]*([0-9\+\-]*)#', $font, $matches);
preg_match('#/UnderlinePosition[\s]*+([0-9\+\-]*+)#', $font, $matches);
$fmetric['underlinePosition'] = intval($matches[1]);
preg_match('#/UnderlineThickness[\s]*([0-9\+\-]*)#', $font, $matches);
preg_match('#/UnderlineThickness[\s]*+([0-9\+\-]*+)#', $font, $matches);
$fmetric['underlineThickness'] = intval($matches[1]);
preg_match('#/isFixedPitch[\s]*([^\s]*)#', $font, $matches);
preg_match('#/isFixedPitch[\s]*+([^\s]*+)#', $font, $matches);
if ($matches[1] == 'true') {
$fmetric['Flags'] |= 1;
}
// get internal map
$imap = array();
if (preg_match_all('#dup[\s]([0-9]+)[\s]*/([^\s]*)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
if (preg_match_all('#dup[\s]([0-9]+)[\s]*+/([^\s]*+)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
foreach ($fmap as $v) {
$imap[$v[2]] = $v[1];
}
@@ -229,22 +230,22 @@ class TCPDF_FONTS {
$eplain .= chr($chr ^ ($r >> 8));
$r = ((($chr + $r) * $c1 + $c2) % 65536);
}
if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) {
if (preg_match('#/ForceBold[\s]*+([^\s]*+)#', $eplain, $matches) > 0) {
if ($matches[1] == 'true') {
$fmetric['Flags'] |= 0x40000;
}
}
if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
if (preg_match('#/StdVW[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
$fmetric['StemV'] = intval($matches[1]);
} else {
$fmetric['StemV'] = 70;
}
if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
if (preg_match('#/StdHW[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
$fmetric['StemH'] = intval($matches[1]);
} else {
$fmetric['StemH'] = 30;
}
if (preg_match('#/BlueValues[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
if (preg_match('#/BlueValues[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
$bv = explode(' ', $matches[1]);
if (count($bv) >= 6) {
$v1 = intval($bv[2]);
@@ -265,7 +266,7 @@ class TCPDF_FONTS {
$fmetric['CapHeight'] = 700;
}
// get the number of random bytes at the beginning of charstrings
if (preg_match('#/lenIV[\s]*([0-9]*)#', $eplain, $matches) > 0) {
if (preg_match('#/lenIV[\s]*+([\d]*+)#', $eplain, $matches) > 0) {
$lenIV = intval($matches[1]);
} else {
$lenIV = 4;
@@ -273,7 +274,7 @@ class TCPDF_FONTS {
$fmetric['Leading'] = 0;
// get charstring data
$eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
preg_match_all('#/([A-Za-z0-9\.]*)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
preg_match_all('#/([A-Za-z0-9\.]*+)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
if (!empty($enc) AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
$enc_map = TCPDF_FONT_DATA::$encmap[$enc];
} else {
@@ -1780,9 +1781,9 @@ class TCPDF_FONTS {
*/
public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
if ($isunicode) {
return array_map(get_called_class().'::unichrUnicode', $ta);
return array_map(static::class.'::unichrUnicode', $ta);
}
return array_map(get_called_class().'::unichrASCII', $ta);
return array_map(static::class.'::unichrASCII', $ta);
}
/**
@@ -2002,7 +2003,7 @@ class TCPDF_FONTS {
if ($isunicode) {
// requires PCRE unicode support turned on
$chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
$carr = array_map(get_called_class().'::uniord', $chars);
$carr = array_map(static::class.'::uniord', $chars);
} else {
$chars = str_split($str);
$carr = array_map('ord', $chars);

View File

@@ -1,13 +1,13 @@
<?php
//============================================================+
// File name : tcpdf_static.php
// Version : 1.1.4
// Version : 1.1.5
// Begin : 2002-08-03
// Last Update : 2023-09-06
// Last Update : 2024-12-23
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2002-2023 Nicola Asuni - Tecnick.com LTD
// Copyright (C) 2002-2025 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
@@ -38,7 +38,7 @@
* This is a PHP class that contains static methods for the TCPDF class.<br>
* @package com.tecnick.tcpdf
* @author Nicola Asuni
* @version 1.1.2
* @version 1.1.5
*/
/**
@@ -46,7 +46,7 @@
* Static methods used by the TCPDF class.
* @package com.tecnick.tcpdf
* @brief PHP class for generating PDF documents without requiring external extensions.
* @version 1.1.1
* @version 1.1.5
* @author Nicola Asuni - info@tecnick.com
*/
class TCPDF_STATIC {
@@ -55,7 +55,7 @@ class TCPDF_STATIC {
* Current TCPDF version.
* @private static
*/
private static $tcpdf_version = '6.7.5';
private static $tcpdf_version = '6.10.0';
/**
* String alias for total number of pages.
@@ -106,6 +106,31 @@ class TCPDF_STATIC {
*/
public static $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
/**
* Array of default cURL options for curl_setopt_array.
*
* @var array<int, bool|int|string> cURL options.
*/
protected const CURLOPT_DEFAULT = [
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_MAXREDIRS => 5,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP | CURLPROTO_FTP | CURLPROTO_FTPS,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_USERAGENT => 'tcpdf',
];
/**
* Array of fixed cURL options for curl_setopt_array.
*
* @var array<int, bool|int|string> cURL options.
*/
protected const CURLOPT_FIXED = [
CURLOPT_FAILONERROR => true,
CURLOPT_RETURNTRANSFER => true,
];
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
@@ -379,7 +404,10 @@ class TCPDF_STATIC {
if (function_exists('posix_getpid')) {
$rnd .= posix_getpid();
}
if (function_exists('openssl_random_pseudo_bytes') AND (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) {
if (function_exists('random_bytes')) {
$rnd .= random_bytes(512);
} elseif (function_exists('openssl_random_pseudo_bytes') AND (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) {
// this is not used on windows systems because it is very slow for a know bug
$rnd .= openssl_random_pseudo_bytes(512);
} else {
@@ -387,7 +415,7 @@ class TCPDF_STATIC {
$rnd .= uniqid('', true);
}
}
return $rnd.$seed.__FILE__.serialize($_SERVER).microtime(true);
return $rnd.$seed.__FILE__.microtime(true);
}
/**
@@ -1820,23 +1848,19 @@ class TCPDF_STATIC {
*/
public static function url_exists($url) {
$crs = curl_init();
// encode query params in URL to get right response form the server
$url = self::encodeUrlQuery($url);
curl_setopt($crs, CURLOPT_URL, $url);
curl_setopt($crs, CURLOPT_NOBODY, true);
curl_setopt($crs, CURLOPT_FAILONERROR, true);
if ((ini_get('open_basedir') == '') && (!ini_get('safe_mode'))) {
curl_setopt($crs, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt($crs, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($crs, CURLOPT_TIMEOUT, 30);
curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file');
curl_setopt($crs, CURLOPT_MAXREDIRS, 5);
if (defined('CURLOPT_PROTOCOLS')) {
curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP | CURLPROTO_FTP | CURLPROTO_FTPS);
}
$curlopts = [];
if (
(ini_get('open_basedir') == '')
&& (ini_get('safe_mode') === ''
|| ini_get('safe_mode') === false)
) {
$curlopts[CURLOPT_FOLLOWLOCATION] = true;
}
$curlopts = array_replace($curlopts, self::CURLOPT_DEFAULT);
$curlopts = array_replace($curlopts, K_CURLOPTS);
$curlopts = array_replace($curlopts, self::CURLOPT_FIXED);
$curlopts[CURLOPT_URL] = $url;
curl_setopt_array($crs, $curlopts);
curl_exec($crs);
$code = curl_getinfo($crs, CURLINFO_HTTP_CODE);
curl_close($crs);
@@ -1957,22 +1981,19 @@ class TCPDF_STATIC {
) {
// try to get remote file data using cURL
$crs = curl_init();
curl_setopt($crs, CURLOPT_URL, $path);
curl_setopt($crs, CURLOPT_BINARYTRANSFER, true);
curl_setopt($crs, CURLOPT_FAILONERROR, true);
curl_setopt($crs, CURLOPT_RETURNTRANSFER, true);
if ((ini_get('open_basedir') == '') && (!ini_get('safe_mode'))) {
curl_setopt($crs, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt($crs, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($crs, CURLOPT_TIMEOUT, 30);
curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file');
curl_setopt($crs, CURLOPT_MAXREDIRS, 5);
if (defined('CURLOPT_PROTOCOLS')) {
curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP | CURLPROTO_FTP | CURLPROTO_FTPS);
$curlopts = [];
if (
(ini_get('open_basedir') == '')
&& (ini_get('safe_mode') === ''
|| ini_get('safe_mode') === false)
) {
$curlopts[CURLOPT_FOLLOWLOCATION] = true;
}
$curlopts = array_replace($curlopts, self::CURLOPT_DEFAULT);
$curlopts = array_replace($curlopts, K_CURLOPTS);
$curlopts = array_replace($curlopts, self::CURLOPT_FIXED);
$curlopts[CURLOPT_URL] = $url;
curl_setopt_array($crs, $curlopts);
$ret = curl_exec($crs);
curl_close($crs);
if ($ret !== false) {
@@ -2631,7 +2652,6 @@ class TCPDF_STATIC {
return $page_mode;
}
} // END OF TCPDF_STATIC CLASS
//============================================================+

View File

@@ -1,13 +1,13 @@
<?php
//============================================================+
// File name : tcpdf.php
// Version : 6.7.5
// Version : 6.10.0
// Begin : 2002-08-03
// Last Update : 2024-03-18
// Last Update : 2025-05-27
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2002-2024 Nicola Asuni - Tecnick.com LTD
// Copyright (C) 2002-2025 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
@@ -104,7 +104,7 @@
* Tools to encode your unicode fonts are on fonts/utils directory.</p>
* @package com.tecnick.tcpdf
* @author Nicola Asuni
* @version 6.6.5
* @version 6.10.0
*/
// TCPDF configuration
@@ -128,7 +128,7 @@ require_once(dirname(__FILE__).'/include/tcpdf_static.php');
* TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
* @package com.tecnick.tcpdf
* @brief PHP class for generating PDF documents without requiring external extensions.
* @version 6.7.5
* @version 6.10.0
* @author Nicola Asuni - info@tecnick.com
* @IgnoreAnnotation("protected")
* @IgnoreAnnotation("public")
@@ -1810,6 +1810,13 @@ class TCPDF {
*/
protected $custom_xmp_rdf = '';
/**
* Custom XMP RDF pdfaextension data.
* @protected
* @since 6.9.0 (2025-02-11)
*/
protected $custom_xmp_rdf_pdfaExtension = '';
/**
* Overprint mode array.
* (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
@@ -3007,6 +3014,7 @@ class TCPDF {
public function Error($msg) {
// unset all class variables
$this->_destroy(true);
$msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
die('<strong>TCPDF ERROR: </strong>'.$msg);
} else {
@@ -4931,6 +4939,32 @@ class TCPDF {
}
}
/**
* Embed the attached files.
* @since 6.9.000 (2025-02-11)
* @public
*/
public function EmbedFile($opt) {
if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
if ((($opt['Subtype'] == 'FileAttachment')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
}
}
}
/**
* Embed the attached files.
* @since 6.9.000 (2025-02-11)
* @public
*/
public function EmbedFileFromString($filename, $content) {
if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
$this->embeddedfiles[$filename] = array('f' => ++$this->n, 'n' => ++$this->n, 'content' => $content );
}
}
/**
* Embedd the attached files.
* @since 4.4.000 (2008-12-07)
@@ -4944,7 +4978,12 @@ class TCPDF {
}
reset($this->embeddedfiles);
foreach ($this->embeddedfiles as $filename => $filedata) {
$data = $this->getCachedFileContents($filedata['file']);
$data = false;
if (isset($filedata['file']) && !empty($filedata['file'])) {
$data = $this->getCachedFileContents($filedata['file']);
} elseif ($filedata['content'] && !empty($filedata['content'])) {
$data = $filedata['content'];
}
if ($data !== FALSE) {
$rawsize = strlen($data);
if ($rawsize > 0) {
@@ -6988,7 +7027,7 @@ class TCPDF {
unset($imgdata);
$imsize = @getimagesize($file);
if ($imsize === FALSE) {
unlink($file);
$this->_unlink($file);
$file = $original_file;
}
}
@@ -7221,7 +7260,7 @@ class TCPDF {
$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
$img->writeImage($tempname);
$info = TCPDF_IMAGES::_parsejpeg($tempname);
unlink($tempname);
$this->_unlink($tempname);
$img->destroy();
} catch(Exception $e) {
$info = false;
@@ -7857,15 +7896,16 @@ class TCPDF {
if ($handle = @opendir(K_PATH_CACHE)) {
while ( false !== ( $file_name = readdir( $handle ) ) ) {
if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
unlink(K_PATH_CACHE.$file_name);
$this->_unlink(K_PATH_CACHE.$file_name);
}
}
closedir($handle);
}
if (isset($this->imagekeys)) {
foreach($this->imagekeys as $file) {
if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) {
@unlink($file);
if ((strpos($file, K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_') === 0)
&& TCPDF_STATIC::file_exists($file)) {
$this->_unlink($file);
}
}
}
@@ -8164,7 +8204,7 @@ class TCPDF {
* @since 5.0.010 (2010-05-17)
*/
protected function _getannotsrefs($n) {
if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
if (!(isset($this->PageAnnots[$n]) OR count($this->empty_signature_appearance)>0 OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
return '';
}
$out = ' /Annots [';
@@ -8310,15 +8350,15 @@ class TCPDF {
break;
}
case 'locked': {
$fval += 1 << 8;
$fval += 1 << 7;
break;
}
case 'togglenoview': {
$fval += 1 << 9;
$fval += 1 << 8;
break;
}
case 'lockedcontents': {
$fval += 1 << 10;
$fval += 1 << 9;
break;
}
default: {
@@ -8532,7 +8572,7 @@ class TCPDF {
}
case 'freetext': {
if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
$annots .= ' /DA ('.$pl['opt']['da'].')';
$annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
}
if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
$annots .= ' /Q '.intval($pl['opt']['q']);
@@ -8789,7 +8829,7 @@ class TCPDF {
$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
}
if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
$annots .= ' /DA ('.$pl['opt']['da'].')';
$annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
}
if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
$annots .= ' /Q '.intval($pl['opt']['q']);
@@ -9628,6 +9668,17 @@ class TCPDF {
$this->custom_xmp_rdf = $xmp;
}
/**
* Set additional XMP data to be added to the default XMP data for PDF/A extensions.
* IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
* @param string $xmp Custom XMP RDF data.
* @since 6.9.0 (2025-02-14)
* @public
*/
public function setExtraXMPPdfaextension($xmp) {
$this->custom_xmp_rdf_pdfaExtension = $xmp;
}
/**
* Put XMP data object and return ID.
* @return int The object ID.
@@ -9762,6 +9813,7 @@ class TCPDF {
$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
$xmp .= $this->custom_xmp_rdf_pdfaExtension;
$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
$xmp .= "\t\t".'</rdf:Description>'."\n";
@@ -9800,7 +9852,11 @@ class TCPDF {
}
// start catalog
$oid = $this->_newobj();
$out = '<< /Type /Catalog';
$out = '<< ';
if (!empty($this->efnames)) {
$out .= ' /AF [ '. implode(' ', $this->efnames) .' ]';
}
$out .= ' /Type /Catalog';
$out .= ' /Version /'.$this->PDFVersion;
//$out .= ' /Extensions <<>>';
$out .= ' /Pages 1 0 R';
@@ -9939,7 +9995,7 @@ class TCPDF {
$out .= ' >> >>';
}
$font = $this->getFontBuffer((($this->pdfa_mode) ? 'pdfa' : '') .'helvetica');
$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
$out .= ' /DA ' . $this->_datastring('/F'.$font['i'].' 0 Tf 0 g');
$out .= ' /Q '.(($this->rtl)?'2':'0');
//$out .= ' /XFA ';
$out .= ' >>';
@@ -11046,7 +11102,7 @@ class TCPDF {
$this->encryptdata['V'] = 4;
$this->encryptdata['Length'] = 128;
$this->encryptdata['CF']['CFM'] = 'AESV2';
$this->encryptdata['CF']['Length'] = 128;
$this->encryptdata['CF']['Length'] = 16;
if ($this->encryptdata['pubkey']) {
$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
$this->encryptdata['Recipients'] = array();
@@ -11057,7 +11113,7 @@ class TCPDF {
$this->encryptdata['V'] = 5;
$this->encryptdata['Length'] = 256;
$this->encryptdata['CF']['CFM'] = 'AESV3';
$this->encryptdata['CF']['Length'] = 256;
$this->encryptdata['CF']['Length'] = 32;
if ($this->encryptdata['pubkey']) {
$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
$this->encryptdata['Recipients'] = array();
@@ -13936,8 +13992,8 @@ class TCPDF {
* @since 3.0.000 (2008-03-27)
*/
protected function addExtGState($parms) {
if ($this->pdfa_mode || $this->pdfa_version >= 2) {
// transparencies are not allowed in PDF/A mode
if (($this->pdfa_mode && $this->pdfa_version < 2) || ($this->state != 2)) {
// transparency is not allowed in PDF/A-1 mode
return;
}
// check if this ExtGState already exist
@@ -16440,7 +16496,7 @@ class TCPDF {
)
);
if(empty($html)) {
if($html === '' || $html === null) {
return $dom;
}
// array of CSS styles ( selector => properties).
@@ -17259,7 +17315,7 @@ class TCPDF {
$hlen = intval(substr($data, 0, $hpos));
$hash = substr($data, $hpos + 1, $hlen);
$encoded = substr($data, $hpos + 2 + $hlen);
if ($hash != $this->hashTCPDFtag($encoded)) {
if (!hash_equals( $this->hashTCPDFtag($encoded), $hash)) {
$this->Error('Invalid parameters');
}
return json_decode(urldecode($encoded), true);
@@ -17425,6 +17481,9 @@ class TCPDF {
}
}
if ($key == $maxel) break;
if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND !empty($dom[$key]['attribute']['id'])) {
$this->setDestination($dom[$key]['attribute']['id']);
}
if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
// check for pagebreak
if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
@@ -18867,6 +18926,29 @@ class TCPDF {
unset($dom);
}
/**
* Check if the path is relative.
* @param string $path path to check
* @return boolean true if the path is relative
* @protected
* @since 6.9.1
*/
protected function isRelativePath($path) {
return (strpos(str_ireplace('%2E', '.', $this->unhtmlentities($path)), '..') !== false);
}
/**
* Check if it contains a non-allowed external protocol.
* @param string $path path to check
* @return boolean true if the protocol is not allowed.
* @protected
* @since 6.9.3
*/
protected function hasExtForbiddenProtocol($path) {
return ((strpos($path, '://') !== false)
&& (preg_match('|^https?://|', $path) !== 1));
}
/**
* Process opening tags.
* @param array $dom html dom array
@@ -19010,29 +19092,29 @@ class TCPDF {
$this->setLineWidth($hrHeight);
$lineStyle = array();
if (isset($tag['fgcolor'])) {
$lineStyle['color'] = $tag['fgcolor'];
}
if (isset($tag['fgcolor'])) {
$lineStyle['color'] = $tag['fgcolor'];
}
if (isset($tag['fgcolor'])) {
$lineStyle['color'] = $tag['fgcolor'];
}
if (isset($tag['fgcolor'])) {
$lineStyle['color'] = $tag['fgcolor'];
}
if (isset($tag['style']['cap'])) {
$lineStyle['cap'] = $tag['style']['cap'];
}
if (isset($tag['style']['cap'])) {
$lineStyle['cap'] = $tag['style']['cap'];
}
if (isset($tag['style']['join'])) {
$lineStyle['join'] = $tag['style']['join'];
}
if (isset($tag['style']['join'])) {
$lineStyle['join'] = $tag['style']['join'];
}
if (isset($tag['style']['dash'])) {
$lineStyle['dash'] = $tag['style']['dash'];
}
if (isset($tag['style']['dash'])) {
$lineStyle['dash'] = $tag['style']['dash'];
}
if (isset($tag['style']['phase'])) {
$lineStyle['phase'] = $tag['style']['phase'];
}
if (isset($tag['style']['phase'])) {
$lineStyle['phase'] = $tag['style']['phase'];
}
$lineStyle = array_filter($lineStyle);
@@ -19055,15 +19137,20 @@ class TCPDF {
if ($imgsrc[0] === '@') {
// data stream
$imgsrc = '@'.base64_decode(substr($imgsrc, 1));
$type = '';
$type = preg_match('/<svg\s+[^>]*[^>]*>.*<\/svg>/is', $imgsrc) ? 'svg' : '';
} else if (preg_match('@^data:image/([^;]*);base64,(.*)@', $imgsrc, $reg)) {
$imgsrc = '@'.base64_decode($reg[2]);
$type = $reg[1];
} elseif ($this->isRelativePath($imgsrc)) {
// accessing parent folders is not allowed
break;
} elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') {
// get image type from a local file path
$imgsrc = substr($imgsrc, 7);
$type = TCPDF_IMAGES::getImageFileType($imgsrc);
} else {
// get image type from a local file path
$imgsrc = substr($imgsrc, 7);
$type = TCPDF_IMAGES::getImageFileType($imgsrc);
} elseif ($this->hasExtForbiddenProtocol($imgsrc)) {
break;
} else {
if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
// fix image path
$findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
@@ -19121,7 +19208,7 @@ class TCPDF {
$imglink = '';
if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
$imglink = $this->HREF['url'];
if ($imglink[0] == '#') {
if ($imglink[0] == '#' AND isset($imglink[1]) AND is_numeric($imglink[1])) {
// convert url to internal link
$lnkdata = explode(',', $imglink);
if (isset($lnkdata[0])) {
@@ -19982,7 +20069,7 @@ class TCPDF {
}
}
if (!$in_table_head) { // we are not inside a thead section
$this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : null;
$this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
// reset row height
$this->resetLastH();
if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
@@ -23170,14 +23257,12 @@ class TCPDF {
$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
// creates a new XML parser to be used by the other XML functions
$parser = xml_parser_create('UTF-8');
// the following function allows to use parser inside object
xml_set_object($parser, $this);
// disable case-folding for this XML parser
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
// sets the element handler functions for the XML parser
xml_set_element_handler($parser, 'startSVGElementHandler', 'endSVGElementHandler');
xml_set_element_handler($parser, [$this, 'startSVGElementHandler'], [$this, 'endSVGElementHandler']);
// sets the character data handler function for the XML parser
xml_set_character_data_handler($parser, 'segSVGContentHandler');
xml_set_character_data_handler($parser, [$this, 'segSVGContentHandler']);
// start parsing an XML document
if (!xml_parse($parser, $svgdata)) {
$error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
@@ -23327,7 +23412,7 @@ class TCPDF {
$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
$this->setTextColorArray($text_color);
// clip
if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
if (preg_match('/rect\(([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)\)/si', $svgstyle['clip'], $regs)) {
$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
@@ -23444,13 +23529,15 @@ class TCPDF {
$cy -= $h;
}
$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
if (count($gradient['stops']) > 1) {
$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
if ((is_array($gradient['stops']) || $gradient['stops'] instanceof Countable) && count($gradient['stops']) > 1) {
$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops']);
}
} elseif ($svgstyle['fill'] != 'none') {
$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
if ($svgstyle['fill-opacity'] != 1) {
$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['fill'], $rgba_matches)) {
$this->setAlpha($this->alpha['CA'], 'Normal', $rgba_matches[1], false);
}
$this->setFillColorArray($fill_color);
if ($svgstyle['fill-rule'] == 'evenodd') {
@@ -23484,7 +23571,7 @@ class TCPDF {
if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
$font_family = $this->getFontFamilyName($regs[1]);
} else {
$font_family = $svgstyle['font-family'];
$font_family = $this->getFontFamilyName($svgstyle['font-family']);
}
if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
$font_size = trim($regs[1]);
@@ -23639,7 +23726,8 @@ class TCPDF {
$params = array();
if (isset($val[2])) {
// get curve parameters
$rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
preg_match_all('/-?\d*\.?\d+/', trim($val[2]), $matches);
$rawparams = $matches[0];
$params = array();
foreach ($rawparams as $ck => $cp) {
$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
@@ -24464,6 +24552,9 @@ class TCPDF {
$img = '@'.base64_decode(substr($img, strlen($m[0])));
} else {
// fix image path
if ($this->isRelativePath($img) || $this->hasExtForbiddenProtocol($img)) {
break;
}
if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
// replace relative path with full server path
$img = $this->svgdir.'/'.$img;
@@ -24784,6 +24875,20 @@ class TCPDF {
return TCPDF_STATIC::file_exists($file);
}
/**
* Wrapper for unlink with disabled protocols.
* @param string $file
* @return bool
*/
protected function _unlink($file)
{
if ((strpos($file, '://') !== false) && ((substr($file, 0, 7) !== 'file://') || (!$this->allowLocalFiles))) {
// forbidden protocol
return false;
}
return @unlink($file);
}
} // END OF TCPDF CLASS
//============================================================+

View File

@@ -3,11 +3,11 @@
// File name : tcpdf_autoconfig.php
// Version : 1.1.1
// Begin : 2013-05-16
// Last Update : 2014-12-18
// Last Update : 2025-04-18
// Authors : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2011-2014 Nicola Asuni - Tecnick.com LTD
// Copyright (C) 2011-2025 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
@@ -37,9 +37,14 @@
* @file
* Try to automatically configure some TCPDF constants if not defined.
* @package com.tecnick.tcpdf
* @version 1.1.1
* @version 1.2.1
*/
// Disable phar stream wrapper globally.
// if (in_array('phar', stream_get_wrappers(), true)) {
// stream_wrapper_unregister('phar');
// }
// DOCUMENT_ROOT fix for IIS Webserver
if ((!isset($_SERVER['DOCUMENT_ROOT'])) OR (empty($_SERVER['DOCUMENT_ROOT']))) {
if(isset($_SERVER['SCRIPT_FILENAME'])) {
@@ -240,6 +245,11 @@ if (!defined('K_TIMEZONE')) {
define('K_TIMEZONE', @date_default_timezone_get());
}
// Custom cURL options for curl_setopt_array.
if (!defined('K_CURLOPTS')) {
define('K_CURLOPTS', array());
}
//============================================================+
// END OF FILE
//============================================================+

View File

@@ -1,104 +0,0 @@
<?php
//============================================================+
// File name : tcpdf_import.php
// Version : 1.0.001
// Begin : 2011-05-23
// Last Update : 2013-09-17
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2011-2013 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
// TCPDF is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// TCPDF 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 Lesser General Public License for more details.
//
// You should have received a copy of the License
// along with TCPDF. If not, see
// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
//
// See LICENSE.TXT file for more information.
// -------------------------------------------------------------------
//
// Description : This is a PHP class extension of the TCPDF library to
// import existing PDF documents.
//
//============================================================+
/**
* @file
* !!! THIS CLASS IS UNDER DEVELOPMENT !!!
* This is a PHP class extension of the TCPDF (http://www.tcpdf.org) library to import existing PDF documents.<br>
* @package com.tecnick.tcpdf
* @author Nicola Asuni
* @version 1.0.001
*/
// include the TCPDF class
require_once(dirname(__FILE__).'/tcpdf.php');
// include PDF parser class
require_once(dirname(__FILE__).'/tcpdf_parser.php');
/**
* @class TCPDF_IMPORT
* !!! THIS CLASS IS UNDER DEVELOPMENT !!!
* PHP class extension of the TCPDF (http://www.tcpdf.org) library to import existing PDF documents.<br>
* @package com.tecnick.tcpdf
* @brief PHP class extension of the TCPDF library to import existing PDF documents.
* @version 1.0.001
* @author Nicola Asuni - info@tecnick.com
*/
class TCPDF_IMPORT extends TCPDF {
/**
* Import an existing PDF document
* @param string $filename Filename of the PDF document to import.
* @return void
* @public
* @since 1.0.000 (2011-05-24)
*/
public function importPDF($filename) {
// load document
$rawdata = file_get_contents($filename);
if ($rawdata === false) {
$this->Error('Unable to get the content of the file: '.$filename);
}
// configuration parameters for parser
$cfg = array(
'die_for_errors' => false,
'ignore_filter_decoding_errors' => true,
'ignore_missing_filter_decoders' => true,
);
try {
// parse PDF data
$pdf = new TCPDF_PARSER($rawdata, $cfg);
} catch (Exception $e) {
die($e->getMessage());
}
// get the parsed data
$data = $pdf->getParsedData();
// release some memory
unset($rawdata);
// ...
print_r($data); // DEBUG
unset($pdf);
}
} // END OF CLASS
//============================================================+
// END OF FILE
//============================================================+

View File

@@ -1,815 +0,0 @@
<?php
//============================================================+
// File name : tcpdf_parser.php
// Version : 1.0.16
// Begin : 2011-05-23
// Last Update : 2015-04-28
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT GNU-LGPLv3
// -------------------------------------------------------------------
// Copyright (C) 2011-2015 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
// TCPDF is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// TCPDF 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 Lesser General Public License for more details.
//
// You should have received a copy of the License
// along with TCPDF. If not, see
// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
//
// See LICENSE.TXT file for more information.
// -------------------------------------------------------------------
//
// Description : This is a PHP class for parsing PDF documents.
//
//============================================================+
/**
* @file
* This is a PHP class for parsing PDF documents.<br>
* @package com.tecnick.tcpdf
* @author Nicola Asuni
* @version 1.0.15
*/
// include class for decoding filters
require_once(dirname(__FILE__).'/include/tcpdf_filters.php');
/**
* @class TCPDF_PARSER
* This is a PHP class for parsing PDF documents.<br>
* @package com.tecnick.tcpdf
* @brief This is a PHP class for parsing PDF documents..
* @version 1.0.15
* @author Nicola Asuni - info@tecnick.com
*/
class TCPDF_PARSER {
/**
* Raw content of the PDF document.
* @private
*/
private $pdfdata = '';
/**
* XREF data.
* @protected
*/
protected $xref = array();
/**
* Array of PDF objects.
* @protected
*/
protected $objects = array();
/**
* Class object for decoding filters.
* @private
*/
private $FilterDecoders;
/**
* Array of configuration parameters.
* @private
*/
private $cfg = array(
'die_for_errors' => false,
'ignore_filter_decoding_errors' => true,
'ignore_missing_filter_decoders' => true,
);
// -----------------------------------------------------------------------------
/**
* Parse a PDF document an return an array of objects.
* @param string $data PDF data to parse.
* @param array $cfg Array of configuration parameters:
* 'die_for_errors' : if true termitate the program execution in case of error, otherwise thows an exception;
* 'ignore_filter_decoding_errors' : if true ignore filter decoding errors;
* 'ignore_missing_filter_decoders' : if true ignore missing filter decoding errors.
* @public
* @since 1.0.000 (2011-05-24)
*/
public function __construct($data, $cfg=array()) {
if (empty($data)) {
$this->Error('Empty PDF data.');
}
// find the pdf header starting position
if (($trimpos = strpos($data, '%PDF-')) === FALSE) {
$this->Error('Invalid PDF data: missing %PDF header.');
}
// get PDF content string
$this->pdfdata = substr($data, $trimpos);
// get length
$pdflen = strlen($this->pdfdata);
// set configuration parameters
$this->setConfig($cfg);
// get xref and trailer data
$this->xref = $this->getXrefData();
// parse all document objects
$this->objects = array();
foreach ($this->xref['xref'] as $obj => $offset) {
if (!isset($this->objects[$obj]) AND ($offset > 0)) {
// decode objects with positive offset
$this->objects[$obj] = $this->getIndirectObject($obj, $offset, true);
}
}
// release some memory
unset($this->pdfdata);
$this->pdfdata = '';
}
/**
* Set the configuration parameters.
* @param array $cfg Array of configuration parameters:
* 'die_for_errors' : if true termitate the program execution in case of error, otherwise thows an exception;
* 'ignore_filter_decoding_errors' : if true ignore filter decoding errors;
* 'ignore_missing_filter_decoders' : if true ignore missing filter decoding errors.
* @public
*/
protected function setConfig($cfg) {
if (isset($cfg['die_for_errors'])) {
$this->cfg['die_for_errors'] = !!$cfg['die_for_errors'];
}
if (isset($cfg['ignore_filter_decoding_errors'])) {
$this->cfg['ignore_filter_decoding_errors'] = !!$cfg['ignore_filter_decoding_errors'];
}
if (isset($cfg['ignore_missing_filter_decoders'])) {
$this->cfg['ignore_missing_filter_decoders'] = !!$cfg['ignore_missing_filter_decoders'];
}
}
/**
* Return an array of parsed PDF document objects.
* @return array Array of parsed PDF document objects.
* @public
* @since 1.0.000 (2011-06-26)
*/
public function getParsedData() {
return array($this->xref, $this->objects);
}
/**
* Get Cross-Reference (xref) table and trailer data from PDF document data.
* @param int $offset xref offset (if know).
* @param array $xref previous xref array (if any).
* @return array containing xref and trailer data.
* @protected
* @since 1.0.000 (2011-05-24)
*/
protected function getXrefData($offset=0, $xref=array()) {
if ($offset == 0) {
// find last startxref
if (preg_match_all('/[\r\n]startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/i', $this->pdfdata, $matches, PREG_SET_ORDER, $offset) == 0) {
$this->Error('Unable to find startxref');
}
$matches = array_pop($matches);
$startxref = $matches[1];
} elseif (strpos($this->pdfdata, 'xref', $offset) == $offset) {
// Already pointing at the xref table
$startxref = $offset;
} elseif (preg_match('/([0-9]+[\s][0-9]+[\s]obj)/i', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset)) {
// Cross-Reference Stream object
$startxref = $offset;
} elseif (preg_match('/[\r\n]startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/i', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset)) {
// startxref found
$startxref = $matches[1][0];
} else {
$this->Error('Unable to find startxref');
}
// check xref position
if (strpos($this->pdfdata, 'xref', $startxref) == $startxref) {
// Cross-Reference
$xref = $this->decodeXref($startxref, $xref);
} else {
// Cross-Reference Stream
$xref = $this->decodeXrefStream($startxref, $xref);
}
if (empty($xref)) {
$this->Error('Unable to find xref');
}
return $xref;
}
/**
* Decode the Cross-Reference section
* @param int $startxref Offset at which the xref section starts (position of the 'xref' keyword).
* @param array $xref Previous xref array (if any).
* @return array containing xref and trailer data.
* @protected
* @since 1.0.000 (2011-06-20)
*/
protected function decodeXref($startxref, $xref=array()) {
$startxref += 4; // 4 is the length of the word 'xref'
// skip initial white space chars: \x00 null (NUL), \x09 horizontal tab (HT), \x0A line feed (LF), \x0C form feed (FF), \x0D carriage return (CR), \x20 space (SP)
$offset = $startxref + strspn($this->pdfdata, "\x00\x09\x0a\x0c\x0d\x20", $startxref);
// initialize object number
$obj_num = 0;
// search for cross-reference entries or subsection
while (preg_match('/([0-9]+)[\x20]([0-9]+)[\x20]?([nf]?)(\r\n|[\x20]?[\r\n])/', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
if ($matches[0][1] != $offset) {
// we are on another section
break;
}
$offset += strlen($matches[0][0]);
if ($matches[3][0] == 'n') {
// create unique object index: [object number]_[generation number]
$index = $obj_num.'_'.intval($matches[2][0]);
// check if object already exist
if (!isset($xref['xref'][$index])) {
// store object offset position
$xref['xref'][$index] = intval($matches[1][0]);
}
++$obj_num;
} elseif ($matches[3][0] == 'f') {
++$obj_num;
} else {
// object number (index)
$obj_num = intval($matches[1][0]);
}
}
// get trailer data
if (preg_match('/trailer[\s]*<<(.*)>>/isU', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
$trailer_data = $matches[1][0];
if (!isset($xref['trailer']) OR empty($xref['trailer'])) {
// get only the last updated version
$xref['trailer'] = array();
// parse trailer_data
if (preg_match('/Size[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) {
$xref['trailer']['size'] = intval($matches[1]);
}
if (preg_match('/Root[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) {
$xref['trailer']['root'] = intval($matches[1]).'_'.intval($matches[2]);
}
if (preg_match('/Encrypt[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) {
$xref['trailer']['encrypt'] = intval($matches[1]).'_'.intval($matches[2]);
}
if (preg_match('/Info[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) {
$xref['trailer']['info'] = intval($matches[1]).'_'.intval($matches[2]);
}
if (preg_match('/ID[\s]*[\[][\s]*[<]([^>]*)[>][\s]*[<]([^>]*)[>]/i', $trailer_data, $matches) > 0) {
$xref['trailer']['id'] = array();
$xref['trailer']['id'][0] = $matches[1];
$xref['trailer']['id'][1] = $matches[2];
}
}
if (preg_match('/Prev[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) {
// get previous xref
$xref = $this->getXrefData(intval($matches[1]), $xref);
}
} else {
$this->Error('Unable to find trailer');
}
return $xref;
}
/**
* Decode the Cross-Reference Stream section
* @param int $startxref Offset at which the xref section starts.
* @param array $xref Previous xref array (if any).
* @return array containing xref and trailer data.
* @protected
* @since 1.0.003 (2013-03-16)
*/
protected function decodeXrefStream($startxref, $xref=array()) {
// try to read Cross-Reference Stream
$xrefobj = $this->getRawObject($startxref);
$xrefcrs = $this->getIndirectObject($xrefobj[1], $startxref, true);
if (!isset($xref['trailer']) OR empty($xref['trailer'])) {
// get only the last updated version
$xref['trailer'] = array();
$filltrailer = true;
} else {
$filltrailer = false;
}
if (!isset($xref['xref'])) {
$xref['xref'] = array();
}
$valid_crs = false;
$columns = 0;
$sarr = $xrefcrs[0][1];
if (!is_array($sarr)) {
$sarr = array();
}
foreach ($sarr as $k => $v) {
if (($v[0] == '/') AND ($v[1] == 'Type') AND (isset($sarr[($k +1)]) AND ($sarr[($k +1)][0] == '/') AND ($sarr[($k +1)][1] == 'XRef'))) {
$valid_crs = true;
} elseif (($v[0] == '/') AND ($v[1] == 'Index') AND (isset($sarr[($k +1)]))) {
// first object number in the subsection
$index_first = intval($sarr[($k +1)][1][0][1]);
// number of entries in the subsection
$index_entries = intval($sarr[($k +1)][1][1][1]);
} elseif (($v[0] == '/') AND ($v[1] == 'Prev') AND (isset($sarr[($k +1)]) AND ($sarr[($k +1)][0] == 'numeric'))) {
// get previous xref offset
$prevxref = intval($sarr[($k +1)][1]);
} elseif (($v[0] == '/') AND ($v[1] == 'W') AND (isset($sarr[($k +1)]))) {
// number of bytes (in the decoded stream) of the corresponding field
$wb = array();
$wb[0] = intval($sarr[($k +1)][1][0][1]);
$wb[1] = intval($sarr[($k +1)][1][1][1]);
$wb[2] = intval($sarr[($k +1)][1][2][1]);
} elseif (($v[0] == '/') AND ($v[1] == 'DecodeParms') AND (isset($sarr[($k +1)][1]))) {
$decpar = $sarr[($k +1)][1];
foreach ($decpar as $kdc => $vdc) {
if (($vdc[0] == '/') AND ($vdc[1] == 'Columns') AND (isset($decpar[($kdc +1)]) AND ($decpar[($kdc +1)][0] == 'numeric'))) {
$columns = intval($decpar[($kdc +1)][1]);
} elseif (($vdc[0] == '/') AND ($vdc[1] == 'Predictor') AND (isset($decpar[($kdc +1)]) AND ($decpar[($kdc +1)][0] == 'numeric'))) {
$predictor = intval($decpar[($kdc +1)][1]);
}
}
} elseif ($filltrailer) {
if (($v[0] == '/') AND ($v[1] == 'Size') AND (isset($sarr[($k +1)]) AND ($sarr[($k +1)][0] == 'numeric'))) {
$xref['trailer']['size'] = $sarr[($k +1)][1];
} elseif (($v[0] == '/') AND ($v[1] == 'Root') AND (isset($sarr[($k +1)]) AND ($sarr[($k +1)][0] == 'objref'))) {
$xref['trailer']['root'] = $sarr[($k +1)][1];
} elseif (($v[0] == '/') AND ($v[1] == 'Info') AND (isset($sarr[($k +1)]) AND ($sarr[($k +1)][0] == 'objref'))) {
$xref['trailer']['info'] = $sarr[($k +1)][1];
} elseif (($v[0] == '/') AND ($v[1] == 'Encrypt') AND (isset($sarr[($k +1)]) AND ($sarr[($k +1)][0] == 'objref'))) {
$xref['trailer']['encrypt'] = $sarr[($k +1)][1];
} elseif (($v[0] == '/') AND ($v[1] == 'ID') AND (isset($sarr[($k +1)]))) {
$xref['trailer']['id'] = array();
$xref['trailer']['id'][0] = $sarr[($k +1)][1][0][1];
$xref['trailer']['id'][1] = $sarr[($k +1)][1][1][1];
}
}
}
// decode data
if ($valid_crs AND isset($xrefcrs[1][3][0])) {
// number of bytes in a row
$rowlen = ($columns + 1);
// convert the stream into an array of integers
$sdata = unpack('C*', $xrefcrs[1][3][0]);
// split the rows
$sdata = array_chunk($sdata, $rowlen);
// initialize decoded array
$ddata = array();
// initialize first row with zeros
$prev_row = array_fill (0, $rowlen, 0);
// for each row apply PNG unpredictor
foreach ($sdata as $k => $row) {
// initialize new row
$ddata[$k] = array();
// get PNG predictor value
$predictor = (10 + $row[0]);
// for each byte on the row
for ($i=1; $i<=$columns; ++$i) {
// new index
$j = ($i - 1);
$row_up = $prev_row[$j];
if ($i == 1) {
$row_left = 0;
$row_upleft = 0;
} else {
$row_left = $row[($i - 1)];
$row_upleft = $prev_row[($j - 1)];
}
switch ($predictor) {
case 10: { // PNG prediction (on encoding, PNG None on all rows)
$ddata[$k][$j] = $row[$i];
break;
}
case 11: { // PNG prediction (on encoding, PNG Sub on all rows)
$ddata[$k][$j] = (($row[$i] + $row_left) & 0xff);
break;
}
case 12: { // PNG prediction (on encoding, PNG Up on all rows)
$ddata[$k][$j] = (($row[$i] + $row_up) & 0xff);
break;
}
case 13: { // PNG prediction (on encoding, PNG Average on all rows)
$ddata[$k][$j] = (($row[$i] + (($row_left + $row_up) / 2)) & 0xff);
break;
}
case 14: { // PNG prediction (on encoding, PNG Paeth on all rows)
// initial estimate
$p = ($row_left + $row_up - $row_upleft);
// distances
$pa = abs($p - $row_left);
$pb = abs($p - $row_up);
$pc = abs($p - $row_upleft);
$pmin = min($pa, $pb, $pc);
// return minimum distance
switch ($pmin) {
case $pa: {
$ddata[$k][$j] = (($row[$i] + $row_left) & 0xff);
break;
}
case $pb: {
$ddata[$k][$j] = (($row[$i] + $row_up) & 0xff);
break;
}
case $pc: {
$ddata[$k][$j] = (($row[$i] + $row_upleft) & 0xff);
break;
}
}
break;
}
default: { // PNG prediction (on encoding, PNG optimum)
$this->Error('Unknown PNG predictor');
break;
}
}
}
$prev_row = $ddata[$k];
} // end for each row
// complete decoding
$sdata = array();
// for every row
foreach ($ddata as $k => $row) {
// initialize new row
$sdata[$k] = array(0, 0, 0);
if ($wb[0] == 0) {
// default type field
$sdata[$k][0] = 1;
}
$i = 0; // count bytes in the row
// for every column
for ($c = 0; $c < 3; ++$c) {
// for every byte on the column
for ($b = 0; $b < $wb[$c]; ++$b) {
if (isset($row[$i])) {
$sdata[$k][$c] += ($row[$i] << (($wb[$c] - 1 - $b) * 8));
}
++$i;
}
}
}
$ddata = array();
// fill xref
if (isset($index_first)) {
$obj_num = $index_first;
} else {
$obj_num = 0;
}
foreach ($sdata as $k => $row) {
switch ($row[0]) {
case 0: { // (f) linked list of free objects
break;
}
case 1: { // (n) objects that are in use but are not compressed
// create unique object index: [object number]_[generation number]
$index = $obj_num.'_'.$row[2];
// check if object already exist
if (!isset($xref['xref'][$index])) {
// store object offset position
$xref['xref'][$index] = $row[1];
}
break;
}
case 2: { // compressed objects
// $row[1] = object number of the object stream in which this object is stored
// $row[2] = index of this object within the object stream
$index = $row[1].'_0_'.$row[2];
$xref['xref'][$index] = -1;
break;
}
default: { // null objects
break;
}
}
++$obj_num;
}
} // end decoding data
if (isset($prevxref)) {
// get previous xref
$xref = $this->getXrefData($prevxref, $xref);
}
return $xref;
}
/**
* Get object type, raw value and offset to next object
* @param int $offset Object offset.
* @return array containing object type, raw value and offset to next object
* @protected
* @since 1.0.000 (2011-06-20)
*/
protected function getRawObject($offset=0) {
$objtype = ''; // object type to be returned
$objval = ''; // object value to be returned
// skip initial white space chars: \x00 null (NUL), \x09 horizontal tab (HT), \x0A line feed (LF), \x0C form feed (FF), \x0D carriage return (CR), \x20 space (SP)
$offset += strspn($this->pdfdata, "\x00\x09\x0a\x0c\x0d\x20", $offset);
// get first char
$char = $this->pdfdata[$offset];
// get object type
switch ($char) {
case '%': { // \x25 PERCENT SIGN
// skip comment and search for next token
$next = strcspn($this->pdfdata, "\r\n", $offset);
if ($next > 0) {
$offset += $next;
return $this->getRawObject($offset);
}
break;
}
case '/': { // \x2F SOLIDUS
// name object
$objtype = $char;
++$offset;
if (preg_match('/^([^\x00\x09\x0a\x0c\x0d\x20\s\x28\x29\x3c\x3e\x5b\x5d\x7b\x7d\x2f\x25]+)/', substr($this->pdfdata, $offset, 256), $matches) == 1) {
$objval = $matches[1]; // unescaped value
$offset += strlen($objval);
}
break;
}
case '(': // \x28 LEFT PARENTHESIS
case ')': { // \x29 RIGHT PARENTHESIS
// literal string object
$objtype = $char;
++$offset;
$strpos = $offset;
if ($char == '(') {
$open_bracket = 1;
while ($open_bracket > 0) {
if (!isset($this->pdfdata[$strpos])) {
break;
}
$ch = $this->pdfdata[$strpos];
switch ($ch) {
case '\\': { // REVERSE SOLIDUS (5Ch) (Backslash)
// skip next character
++$strpos;
break;
}
case '(': { // LEFT PARENHESIS (28h)
++$open_bracket;
break;
}
case ')': { // RIGHT PARENTHESIS (29h)
--$open_bracket;
break;
}
}
++$strpos;
}
$objval = substr($this->pdfdata, $offset, ($strpos - $offset - 1));
$offset = $strpos;
}
break;
}
case '[': // \x5B LEFT SQUARE BRACKET
case ']': { // \x5D RIGHT SQUARE BRACKET
// array object
$objtype = $char;
++$offset;
if ($char == '[') {
// get array content
$objval = array();
do {
// get element
$element = $this->getRawObject($offset);
$offset = $element[2];
$objval[] = $element;
} while ($element[0] != ']');
// remove closing delimiter
array_pop($objval);
}
break;
}
case '<': // \x3C LESS-THAN SIGN
case '>': { // \x3E GREATER-THAN SIGN
if (isset($this->pdfdata[($offset + 1)]) AND ($this->pdfdata[($offset + 1)] == $char)) {
// dictionary object
$objtype = $char.$char;
$offset += 2;
if ($char == '<') {
// get array content
$objval = array();
do {
// get element
$element = $this->getRawObject($offset);
$offset = $element[2];
$objval[] = $element;
} while ($element[0] != '>>');
// remove closing delimiter
array_pop($objval);
}
} else {
// hexadecimal string object
$objtype = $char;
++$offset;
if (($char == '<') AND (preg_match('/^([0-9A-Fa-f\x09\x0a\x0c\x0d\x20]+)>/iU', substr($this->pdfdata, $offset), $matches) == 1)) {
// remove white space characters
$objval = strtr($matches[1], "\x09\x0a\x0c\x0d\x20", '');
$offset += strlen($matches[0]);
} elseif (($endpos = strpos($this->pdfdata, '>', $offset)) !== FALSE) {
$offset = $endpos + 1;
}
}
break;
}
default: {
if (substr($this->pdfdata, $offset, 6) == 'endobj') {
// indirect object
$objtype = 'endobj';
$offset += 6;
} elseif (substr($this->pdfdata, $offset, 4) == 'null') {
// null object
$objtype = 'null';
$offset += 4;
$objval = 'null';
} elseif (substr($this->pdfdata, $offset, 4) == 'true') {
// boolean true object
$objtype = 'boolean';
$offset += 4;
$objval = 'true';
} elseif (substr($this->pdfdata, $offset, 5) == 'false') {
// boolean false object
$objtype = 'boolean';
$offset += 5;
$objval = 'false';
} elseif (substr($this->pdfdata, $offset, 6) == 'stream') {
// start stream object
$objtype = 'stream';
$offset += 6;
if (preg_match('/^([\r]?[\n])/isU', substr($this->pdfdata, $offset), $matches) == 1) {
$offset += strlen($matches[0]);
if (preg_match('/(endstream)[\x09\x0a\x0c\x0d\x20]/isU', substr($this->pdfdata, $offset), $matches, PREG_OFFSET_CAPTURE) == 1) {
$objval = substr($this->pdfdata, $offset, $matches[0][1]);
$offset += $matches[1][1];
}
}
} elseif (substr($this->pdfdata, $offset, 9) == 'endstream') {
// end stream object
$objtype = 'endstream';
$offset += 9;
} elseif (preg_match('/^([0-9]+)[\s]+([0-9]+)[\s]+R/iU', substr($this->pdfdata, $offset, 33), $matches) == 1) {
// indirect object reference
$objtype = 'objref';
$offset += strlen($matches[0]);
$objval = intval($matches[1]).'_'.intval($matches[2]);
} elseif (preg_match('/^([0-9]+)[\s]+([0-9]+)[\s]+obj/iU', substr($this->pdfdata, $offset, 33), $matches) == 1) {
// object start
$objtype = 'obj';
$objval = intval($matches[1]).'_'.intval($matches[2]);
$offset += strlen ($matches[0]);
} elseif (($numlen = strspn($this->pdfdata, '+-.0123456789', $offset)) > 0) {
// numeric object
$objtype = 'numeric';
$objval = substr($this->pdfdata, $offset, $numlen);
$offset += $numlen;
}
break;
}
}
return array($objtype, $objval, $offset);
}
/**
* Get content of indirect object.
* @param string $obj_ref Object number and generation number separated by underscore character.
* @param int $offset Object offset.
* @param boolean $decoding If true decode streams.
* @return array containing object data.
* @protected
* @since 1.0.000 (2011-05-24)
*/
protected function getIndirectObject($obj_ref, $offset=0, $decoding=true) {
$obj = explode('_', $obj_ref);
if (($obj === false) OR (count($obj) != 2)) {
$this->Error('Invalid object reference: '.$obj);
return;
}
$objref = $obj[0].' '.$obj[1].' obj';
// ignore leading zeros
$offset += strspn($this->pdfdata, '0', $offset);
if (strpos($this->pdfdata, $objref, $offset) != $offset) {
// an indirect reference to an undefined object shall be considered a reference to the null object
return array('null', 'null', $offset);
}
// starting position of object content
$offset += strlen($objref);
// get array of object content
$objdata = array();
$i = 0; // object main index
do {
$oldoffset = $offset;
// get element
$element = $this->getRawObject($offset);
$offset = $element[2];
// decode stream using stream's dictionary information
if ($decoding AND ($element[0] == 'stream') AND (isset($objdata[($i - 1)][0])) AND ($objdata[($i - 1)][0] == '<<')) {
$element[3] = $this->decodeStream($objdata[($i - 1)][1], $element[1]);
}
$objdata[$i] = $element;
++$i;
} while (($element[0] != 'endobj') AND ($offset != $oldoffset));
// remove closing delimiter
array_pop($objdata);
// return raw object content
return $objdata;
}
/**
* Get the content of object, resolving indect object reference if necessary.
* @param string $obj Object value.
* @return array containing object data.
* @protected
* @since 1.0.000 (2011-06-26)
*/
protected function getObjectVal($obj) {
if ($obj[0] == 'objref') {
// reference to indirect object
if (isset($this->objects[$obj[1]])) {
// this object has been already parsed
return $this->objects[$obj[1]];
} elseif (isset($this->xref[$obj[1]])) {
// parse new object
$this->objects[$obj[1]] = $this->getIndirectObject($obj[1], $this->xref[$obj[1]], false);
return $this->objects[$obj[1]];
}
}
return $obj;
}
/**
* Decode the specified stream.
* @param array $sdic Stream's dictionary array.
* @param string $stream Stream to decode.
* @return array containing decoded stream data and remaining filters.
* @protected
* @since 1.0.000 (2011-06-22)
*/
protected function decodeStream($sdic, $stream) {
// get stream length and filters
$slength = strlen($stream);
if ($slength <= 0) {
return array('', array());
}
$filters = array();
foreach ($sdic as $k => $v) {
if ($v[0] == '/') {
if (($v[1] == 'Length') AND (isset($sdic[($k + 1)])) AND ($sdic[($k + 1)][0] == 'numeric')) {
// get declared stream length
$declength = intval($sdic[($k + 1)][1]);
if ($declength < $slength) {
$stream = substr($stream, 0, $declength);
$slength = $declength;
}
} elseif (($v[1] == 'Filter') AND (isset($sdic[($k + 1)]))) {
// resolve indirect object
$objval = $this->getObjectVal($sdic[($k + 1)]);
if ($objval[0] == '/') {
// single filter
$filters[] = $objval[1];
} elseif ($objval[0] == '[') {
// array of filters
foreach ($objval[1] as $flt) {
if ($flt[0] == '/') {
$filters[] = $flt[1];
}
}
}
}
}
}
// decode the stream
$remaining_filters = array();
foreach ($filters as $filter) {
if (in_array($filter, TCPDF_FILTERS::getAvailableFilters())) {
try {
$stream = TCPDF_FILTERS::decodeFilter($filter, $stream);
} catch (Exception $e) {
$emsg = $e->getMessage();
if ((($emsg[0] == '~') AND !$this->cfg['ignore_missing_filter_decoders'])
OR (($emsg[0] != '~') AND !$this->cfg['ignore_filter_decoding_errors'])) {
$this->Error($e->getMessage());
}
}
} else {
// add missing filter to array
$remaining_filters[] = $filter;
}
}
return array($stream, $remaining_filters);
}
/**
* Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
* @param string $msg The error message
* @public
* @since 1.0.000 (2011-05-23)
*/
public function Error($msg) {
if ($this->cfg['die_for_errors']) {
die('<strong>TCPDF_PARSER ERROR: </strong>'.$msg);
} else {
throw new Exception('TCPDF_PARSER ERROR: '.$msg);
}
}
} // END OF TCPDF_PARSER CLASS
//============================================================+
// END OF FILE
//============================================================+

0
lib/tecnickcom/tcpdf/tools/tcpdf_addfont.php Normal file → Executable file
View File

View File

@@ -1,4 +1,66 @@
# 3.16.0 (2024-XX-XX)
# 3.21.1 (2025-05-03)
* Fix ExtensionSet usage of BinaryOperatorExpressionParser
# 3.21.0 (2025-05-02)
* Fix wrong array index
* Deprecate `Template::loadTemplate()`
* Fix testing and expression when it evaluates to an instance of `Markup`
* Add `ReturnPrimitiveTypeInterface` (and sub-interfaces for number, boolean, string, and array)
* Add `SupportDefinedTestInterface` for expression nodes supporting the `defined` test
* Deprecate using the `|` operator in an expression with `+` or `-` without using parentheses to clarify precedence
* Deprecate operator precedence outside of the [0, 512] range
* Introduce expression parser classes to describe operators and operands provided by extensions
instead of arrays (it comes with many deprecations that are documented in
the ``deprecated`` documentation chapter)
* Deprecate the `Twig\ExpressionParser`, and `Twig\OperatorPrecedenceChange` classes
* Add attributes `AsTwigFilter`, `AsTwigFunction`, and `AsTwigTest` to ease extension development
# 3.20.0 (2025-02-13)
* Fix support for ignoring syntax errors in an undefined handler in guard
* Add configuration for Commonmark
* Fix wrong array index
* Bump minimum PHP version to 8.1
* Add support for registering callbacks for undefined functions, filters or token parsers in the IntegrationTestCase
* Use correct line number for `ForElseNode`
* Fix timezone conversion on strings
# 3.19.0 (2025-01-28)
* Fix a security issue where escaping was missing when using `??`
* Deprecate `Token::getType()`, use `Token::test()` instead
* Add `Token::toEnglish()`
* Add `ForElseNode`
* Deprecate `Twig\ExpressionParser::parseOnlyArguments()` and
`Twig\ExpressionParser::parseArguments()` (use
`Twig\ExpressionParser::parseNamedArguments()` instead)
* Fix `constant()` behavior when used with `??`
* Add the `invoke` filter
* Make `{}` optional for the `types` tag
* Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes
* Ignore static properties when using the dot operator
# 3.18.0 (2024-12-29)
* Fix unary operator precedence change
* Ignore `SyntaxError` exceptions from undefined handlers when using the `guard` tag
* Add a way to stream template rendering (`TemplateWrapper::stream()` and `TemplateWrapper::streamBlock()`)
# 3.17.1 (2024-12-12)
* Fix the null coalescing operator when the test returns null
* Fix the Elvis operator when used as '? :' instead of '?:'
* Support for invoking closures
# 3.17.0 (2024-12-10)
* Fix ArrayAccess with objects as keys
* Support underscores in number literals
* Deprecate `ConditionalExpression` and `NullCoalesceExpression` (use `ConditionalTernary` and `NullCoalesceBinary` instead)
# 3.16.0 (2024-11-29)
* Deprecate `InlinePrint`
* Fix having macro variables starting with an underscore
@@ -6,7 +68,7 @@
* Deprecate returning `null` from `TwigFilter::getSafe()` and `TwigFunction::getSafe()`, return `[]` instead
# 3.15.0 (2024-11-17)
* [BC BREAK] Add support for accessing class constants with the dot operator;
this can be a BC break if you don't use UPPERCASE constant names
* Add Spanish inflector support for the `plural` and `singular` filters in the String extension

View File

@@ -24,11 +24,10 @@
}
],
"require": {
"php": ">=8.0.2",
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-php81": "^1.29"
"symfony/polyfill-ctype": "^1.8"
},
"require-dev": {
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0",

View File

@@ -6,12 +6,6 @@ parameters:
count: 1
path: src/Extension/CoreExtension.php
- # Avoid BC-break
message: '#^Constructor of class Twig\\Node\\ForNode has an unused parameter \$ifexpr\.$#'
identifier: constructor.unusedParameter
count: 1
path: src/Node/ForNode.php
- # 2 parameters will be required
message: '#^Method Twig\\Node\\IncludeNode\:\:addGetTemplate\(\) invoked with 2 parameters, 1 required\.$#'
identifier: arguments.count
@@ -23,3 +17,9 @@ parameters:
identifier: parameter.phpDocType
count: 5
path: src/Node/Node.php
- # Adding 0 to the string representation of a number is valid and what we want here
message: '#^Binary operation "\+" between 0 and string results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: src/Lexer.php

View File

@@ -79,6 +79,9 @@ abstract class AbstractTwigCallable implements TwigCallableInterface
return $this->dynamicName;
}
/**
* @return callable|array{class-string, string}|null
*/
public function getCallable()
{
return $this->callable;

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Attribute;
use Twig\DeprecatedCallableInfo;
use Twig\TwigFilter;
/**
* Registers a method as template filter.
*
* If the first argument of the method has Twig\Environment type-hint, the filter will receive the current environment.
* Additional arguments of the method come from the filter call.
*
* #[AsTwigFilter(name: 'foo')]
* function fooFilter(Environment $env, $string, $arg1 = null, ...) { ... }
*
* {{ 'string'|foo(arg1) }}
*
* @see TwigFilter
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AsTwigFilter
{
/**
* @param non-empty-string $name The name of the filter in Twig.
* @param bool|null $needsCharset Whether the filter needs the charset passed as the first argument.
* @param bool|null $needsEnvironment Whether the filter needs the environment passed as the first argument, or after the charset.
* @param bool|null $needsContext Whether the filter needs the context array passed as the first argument, or after the charset and the environment.
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped.
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the filter is safe.
* @param string|null $preEscape Some filters may need to work on input that is already escaped or safe
* @param string[]|null $preservesSafety Preserves the safety of the value that the filter is applied to.
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
*/
public function __construct(
public string $name,
public ?bool $needsCharset = null,
public ?bool $needsEnvironment = null,
public ?bool $needsContext = null,
public ?array $isSafe = null,
public string|array|null $isSafeCallback = null,
public ?string $preEscape = null,
public ?array $preservesSafety = null,
public ?DeprecatedCallableInfo $deprecationInfo = null,
) {
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Attribute;
use Twig\DeprecatedCallableInfo;
use Twig\TwigFunction;
/**
* Registers a method as template function.
*
* If the first argument of the method has Twig\Environment type-hint, the function will receive the current environment.
* Additional arguments of the method come from the function call.
*
* #[AsTwigFunction(name: 'foo')]
* function fooFunction(Environment $env, string $string, $arg1 = null, ...) { ... }
*
* {{ foo('string', arg1) }}
*
* @see TwigFunction
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AsTwigFunction
{
/**
* @param non-empty-string $name The name of the function in Twig.
* @param bool|null $needsCharset Whether the function needs the charset passed as the first argument.
* @param bool|null $needsEnvironment Whether the function needs the environment passed as the first argument, or after the charset.
* @param bool|null $needsContext Whether the function needs the context array passed as the first argument, or after the charset and the environment.
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped.
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the function is safe.
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
*/
public function __construct(
public string $name,
public ?bool $needsCharset = null,
public ?bool $needsEnvironment = null,
public ?bool $needsContext = null,
public ?array $isSafe = null,
public string|array|null $isSafeCallback = null,
public ?DeprecatedCallableInfo $deprecationInfo = null,
) {
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Attribute;
use Twig\DeprecatedCallableInfo;
use Twig\TwigTest;
/**
* Registers a method as template test.
*
* The first argument is the value to test and the other arguments are the
* arguments passed to the test in the template.
*
* #[AsTwigTest(name: 'foo')]
* public function fooTest($value, $arg1 = null) { ... }
*
* {% if value is foo(arg1) %}
*
* @see TwigTest
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AsTwigTest
{
/**
* @param non-empty-string $name The name of the test in Twig.
* @param bool|null $needsCharset Whether the test needs the charset passed as the first argument.
* @param bool|null $needsEnvironment Whether the test needs the environment passed as the first argument, or after the charset.
* @param bool|null $needsContext Whether the test needs the context array passed as the first argument, or after the charset and the environment.
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
*/
public function __construct(
public string $name,
public ?bool $needsCharset = null,
public ?bool $needsEnvironment = null,
public ?bool $needsContext = null,
public ?DeprecatedCallableInfo $deprecationInfo = null,
) {
}
}

View File

@@ -74,7 +74,7 @@ class Compiler
$node->compile($this);
if ($this->didUseEcho) {
trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, \get_class($node));
trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, $node::class);
}
return $this;
@@ -99,7 +99,7 @@ class Compiler
$node->compile($this);
if ($this->didUseEcho) {
trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, \get_class($node));
trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, $node::class);
}
return $this;
@@ -170,7 +170,7 @@ class Compiler
} elseif (\is_bool($value)) {
$this->raw($value ? 'true' : 'false');
} elseif (\is_array($value)) {
$this->raw('array(');
$this->raw('[');
$first = true;
foreach ($value as $key => $v) {
if (!$first) {
@@ -181,7 +181,7 @@ class Compiler
$this->raw(' => ');
$this->repr($v);
}
$this->raw(')');
$this->raw(']');
} else {
$this->string($value);
}

View File

@@ -19,6 +19,7 @@ use Twig\Error\Error;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\ExpressionParser\ExpressionParsers;
use Twig\Extension\CoreExtension;
use Twig\Extension\EscaperExtension;
use Twig\Extension\ExtensionInterface;
@@ -27,8 +28,6 @@ use Twig\Extension\YieldNotReadyExtension;
use Twig\Loader\ArrayLoader;
use Twig\Loader\ChainLoader;
use Twig\Loader\LoaderInterface;
use Twig\Node\Expression\Binary\AbstractBinary;
use Twig\Node\Expression\Unary\AbstractUnary;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
@@ -44,11 +43,11 @@ use Twig\TokenParser\TokenParserInterface;
*/
class Environment
{
public const VERSION = '3.16.0';
public const VERSION_ID = 31600;
public const VERSION = '3.21.1';
public const VERSION_ID = 32101;
public const MAJOR_VERSION = 3;
public const MINOR_VERSION = 16;
public const RELEASE_VERSION = 0;
public const MINOR_VERSION = 21;
public const RELEASE_VERSION = 1;
public const EXTRA_VERSION = '';
private $charset;
@@ -155,6 +154,8 @@ class Environment
/**
* Enables debugging mode.
*
* @return void
*/
public function enableDebug()
{
@@ -164,6 +165,8 @@ class Environment
/**
* Disables debugging mode.
*
* @return void
*/
public function disableDebug()
{
@@ -183,6 +186,8 @@ class Environment
/**
* Enables the auto_reload option.
*
* @return void
*/
public function enableAutoReload()
{
@@ -191,6 +196,8 @@ class Environment
/**
* Disables the auto_reload option.
*
* @return void
*/
public function disableAutoReload()
{
@@ -209,6 +216,8 @@ class Environment
/**
* Enables the strict_variables option.
*
* @return void
*/
public function enableStrictVariables()
{
@@ -218,6 +227,8 @@ class Environment
/**
* Disables the strict_variables option.
*
* @return void
*/
public function disableStrictVariables()
{
@@ -267,6 +278,8 @@ class Environment
* @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation,
* an absolute path to the compiled templates,
* or false to disable cache
*
* @return void
*/
public function setCache($cache)
{
@@ -503,6 +516,9 @@ class Environment
throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
}
/**
* @return void
*/
public function setLexer(Lexer $lexer)
{
$this->lexer = $lexer;
@@ -520,6 +536,9 @@ class Environment
return $this->lexer->tokenize($source);
}
/**
* @return void
*/
public function setParser(Parser $parser)
{
$this->parser = $parser;
@@ -539,6 +558,9 @@ class Environment
return $this->parser->parse($stream);
}
/**
* @return void
*/
public function setCompiler(Compiler $compiler)
{
$this->compiler = $compiler;
@@ -573,6 +595,9 @@ class Environment
}
}
/**
* @return void
*/
public function setLoader(LoaderInterface $loader)
{
$this->loader = $loader;
@@ -583,6 +608,9 @@ class Environment
return $this->loader;
}
/**
* @return void
*/
public function setCharset(string $charset)
{
if ('UTF8' === $charset = strtoupper($charset ?: '')) {
@@ -603,6 +631,9 @@ class Environment
return $this->extensionSet->hasExtension($class);
}
/**
* @return void
*/
public function addRuntimeLoader(RuntimeLoaderInterface $loader)
{
$this->runtimeLoaders[] = $loader;
@@ -650,6 +681,9 @@ class Environment
throw new RuntimeError(\sprintf('Unable to load the "%s" runtime.', $class));
}
/**
* @return void
*/
public function addExtension(ExtensionInterface $extension)
{
$this->extensionSet->addExtension($extension);
@@ -658,6 +692,8 @@ class Environment
/**
* @param ExtensionInterface[] $extensions An array of extensions
*
* @return void
*/
public function setExtensions(array $extensions)
{
@@ -673,6 +709,9 @@ class Environment
return $this->extensionSet->getExtensions();
}
/**
* @return void
*/
public function addTokenParser(TokenParserInterface $parser)
{
$this->extensionSet->addTokenParser($parser);
@@ -696,11 +735,17 @@ class Environment
return $this->extensionSet->getTokenParser($name);
}
/**
* @param callable(string): (TokenParserInterface|false) $callable
*/
public function registerUndefinedTokenParserCallback(callable $callable): void
{
$this->extensionSet->registerUndefinedTokenParserCallback($callable);
}
/**
* @return void
*/
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
$this->extensionSet->addNodeVisitor($visitor);
@@ -716,6 +761,9 @@ class Environment
return $this->extensionSet->getNodeVisitors();
}
/**
* @return void
*/
public function addFilter(TwigFilter $filter)
{
$this->extensionSet->addFilter($filter);
@@ -729,6 +777,9 @@ class Environment
return $this->extensionSet->getFilter($name);
}
/**
* @param callable(string): (TwigFilter|false) $callable
*/
public function registerUndefinedFilterCallback(callable $callable): void
{
$this->extensionSet->registerUndefinedFilterCallback($callable);
@@ -750,6 +801,9 @@ class Environment
return $this->extensionSet->getFilters();
}
/**
* @return void
*/
public function addTest(TwigTest $test)
{
$this->extensionSet->addTest($test);
@@ -773,6 +827,9 @@ class Environment
return $this->extensionSet->getTest($name);
}
/**
* @return void
*/
public function addFunction(TwigFunction $function)
{
$this->extensionSet->addFunction($function);
@@ -786,6 +843,9 @@ class Environment
return $this->extensionSet->getFunction($name);
}
/**
* @param callable(string): (TwigFunction|false) $callable
*/
public function registerUndefinedFunctionCallback(callable $callable): void
{
$this->extensionSet->registerUndefinedFunctionCallback($callable);
@@ -814,6 +874,8 @@ class Environment
* but after, you can only update existing globals.
*
* @param mixed $value The global value
*
* @return void
*/
public function addGlobal(string $name, $value)
{
@@ -862,22 +924,10 @@ class Environment
/**
* @internal
*
* @return array<string, array{precedence: int, class: class-string<AbstractUnary>}>
*/
public function getUnaryOperators(): array
public function getExpressionParsers(): ExpressionParsers
{
return $this->extensionSet->getUnaryOperators();
}
/**
* @internal
*
* @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
*/
public function getBinaryOperators(): array
{
return $this->extensionSet->getBinaryOperators();
return $this->extensionSet->getExpressionParsers();
}
private function updateOptionsHash(): void

View File

@@ -29,20 +29,17 @@ use Twig\Template;
* Whenever possible, you must set these information (original template name
* and line number) yourself by passing them to the constructor. If some or all
* these information are not available from where you throw the exception, then
* this class will guess them automatically (when the line number is set to -1
* and/or the name is set to null). As this is a costly operation, this
* can be disabled by passing false for both the name and the line number
* when creating a new instance of this class.
* this class will guess them automatically.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Error extends \Exception
{
private $lineno;
private $name;
private $rawMessage;
private $sourcePath;
private $sourceCode;
private ?Source $source;
private string $phpFile;
private int $phpLine;
/**
* Constructor.
@@ -57,16 +54,10 @@ class Error extends \Exception
{
parent::__construct('', 0, $previous);
if (null === $source) {
$name = null;
} else {
$name = $source->getName();
$this->sourceCode = $source->getCode();
$this->sourcePath = $source->getPath();
}
$this->phpFile = $this->getFile();
$this->phpLine = $this->getLine();
$this->lineno = $lineno;
$this->name = $name;
$this->source = $source;
$this->rawMessage = $message;
$this->updateRepr();
}
@@ -84,30 +75,26 @@ class Error extends \Exception
public function setTemplateLine(int $lineno): void
{
$this->lineno = $lineno;
$this->updateRepr();
}
public function getSourceContext(): ?Source
{
return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null;
return $this->source;
}
public function setSourceContext(?Source $source = null): void
{
if (null === $source) {
$this->sourceCode = $this->name = $this->sourcePath = null;
} else {
$this->sourceCode = $source->getCode();
$this->name = $source->getName();
$this->sourcePath = $source->getPath();
}
$this->source = $source;
$this->updateRepr();
}
public function guess(): void
{
if ($this->lineno > -1) {
return;
}
$this->guessTemplateInfo();
$this->updateRepr();
}
@@ -120,82 +107,47 @@ class Error extends \Exception
private function updateRepr(): void
{
$this->message = $this->rawMessage;
if ($this->sourcePath && $this->lineno > 0) {
$this->file = $this->sourcePath;
$this->line = $this->lineno;
return;
}
$dot = false;
if (str_ends_with($this->message, '.')) {
$this->message = substr($this->message, 0, -1);
$dot = true;
}
$questionMark = false;
if (str_ends_with($this->message, '?')) {
$this->message = substr($this->message, 0, -1);
$questionMark = true;
}
if ($this->name) {
if (\is_string($this->name) || $this->name instanceof \Stringable) {
$name = \sprintf('"%s"', $this->name);
if ($this->source && $this->source->getPath()) {
// we only update the file and the line together
$this->file = $this->source->getPath();
if ($this->lineno > 0) {
$this->line = $this->lineno;
} else {
$name = json_encode($this->name);
$this->line = -1;
}
$this->message .= \sprintf(' in %s', $name);
}
if ($this->lineno && $this->lineno >= 0) {
$this->message = $this->rawMessage;
$last = substr($this->message, -1);
if ($punctuation = '.' === $last || '?' === $last ? $last : '') {
$this->message = substr($this->message, 0, -1);
}
if ($this->source && $this->source->getName()) {
$this->message .= \sprintf(' in "%s"', $this->source->getName());
}
if ($this->lineno > 0) {
$this->message .= \sprintf(' at line %d', $this->lineno);
}
if ($dot) {
$this->message .= '.';
}
if ($questionMark) {
$this->message .= '?';
if ($punctuation) {
$this->message .= $punctuation;
}
}
private function guessTemplateInfo(): void
{
$template = null;
$templateClass = null;
// $this->source is never null here (see guess() usage in Template)
$this->lineno = 0;
$template = null;
$backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Template) {
$currentClass = \get_class($trace['object']);
$isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass);
if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object'];
$templateClass = \get_class($trace['object']);
}
if (isset($trace['object']) && $trace['object'] instanceof Template && $this->source->getName() === $trace['object']->getTemplateName()) {
$template = $trace['object'];
break;
}
}
// update template name
if (null !== $template && null === $this->name) {
$this->name = $template->getTemplateName();
}
// update template path if any
if (null !== $template && null === $this->sourcePath) {
$src = $template->getSourceContext();
$this->sourceCode = $src->getCode();
$this->sourcePath = $src->getPath();
}
if (null === $template || $this->lineno > -1) {
return;
}
$r = new \ReflectionObject($template);
$file = $r->getFileName();
@@ -206,8 +158,7 @@ class Error extends \Exception
while ($e = array_pop($exceptions)) {
$traces = $e->getTrace();
array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]);
array_unshift($traces, ['file' => $e instanceof Error ? $e->phpFile : $e->getFile(), 'line' => $e instanceof Error ? $e->phpLine : $e->getLine()]);
while ($trace = array_shift($traces)) {
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
continue;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser;
abstract class AbstractExpressionParser implements ExpressionParserInterface
{
public function __toString(): string
{
return \sprintf('%s(%s)', ExpressionParserType::getType($this)->value, $this->getName());
}
public function getPrecedenceChange(): ?PrecedenceChange
{
return null;
}
public function getAliases(): array
{
return [];
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser;
interface ExpressionParserDescriptionInterface
{
public function getDescription(): string;
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser;
interface ExpressionParserInterface
{
public function __toString(): string;
public function getName(): string;
public function getPrecedence(): int;
public function getPrecedenceChange(): ?PrecedenceChange;
/**
* @return array<string>
*/
public function getAliases(): array;
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser;
/**
* @internal
*/
enum ExpressionParserType: string
{
case Prefix = 'prefix';
case Infix = 'infix';
public static function getType(object $object): ExpressionParserType
{
if ($object instanceof PrefixExpressionParserInterface) {
return self::Prefix;
}
if ($object instanceof InfixExpressionParserInterface) {
return self::Infix;
}
throw new \InvalidArgumentException(\sprintf('Unsupported expression parser type: %s', $object::class));
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser;
/**
* @template-implements \IteratorAggregate<ExpressionParserInterface>
*
* @internal
*/
final class ExpressionParsers implements \IteratorAggregate
{
/**
* @var array<class-string<ExpressionParserInterface>, array<string, ExpressionParserInterface>>
*/
private array $parsersByName = [];
/**
* @var array<class-string<ExpressionParserInterface>, ExpressionParserInterface>
*/
private array $parsersByClass = [];
/**
* @var \WeakMap<ExpressionParserInterface, array<ExpressionParserInterface>>|null
*/
private ?\WeakMap $precedenceChanges = null;
/**
* @param array<ExpressionParserInterface> $parsers
*/
public function __construct(array $parsers = [])
{
$this->add($parsers);
}
/**
* @param array<ExpressionParserInterface> $parsers
*
* @return $this
*/
public function add(array $parsers): static
{
foreach ($parsers as $parser) {
if ($parser->getPrecedence() > 512 || $parser->getPrecedence() < 0) {
trigger_deprecation('twig/twig', '3.21', 'Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence());
// throw new \InvalidArgumentException(\sprintf('Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence()));
}
$interface = $parser instanceof PrefixExpressionParserInterface ? PrefixExpressionParserInterface::class : InfixExpressionParserInterface::class;
$this->parsersByName[$interface][$parser->getName()] = $parser;
$this->parsersByClass[$parser::class] = $parser;
foreach ($parser->getAliases() as $alias) {
$this->parsersByName[$interface][$alias] = $parser;
}
}
return $this;
}
/**
* @template T of ExpressionParserInterface
*
* @param class-string<T> $class
*
* @return T|null
*/
public function getByClass(string $class): ?ExpressionParserInterface
{
return $this->parsersByClass[$class] ?? null;
}
/**
* @template T of ExpressionParserInterface
*
* @param class-string<T> $interface
*
* @return T|null
*/
public function getByName(string $interface, string $name): ?ExpressionParserInterface
{
return $this->parsersByName[$interface][$name] ?? null;
}
public function getIterator(): \Traversable
{
foreach ($this->parsersByName as $parsers) {
// we don't yield the keys
yield from $parsers;
}
}
/**
* @internal
*
* @return \WeakMap<ExpressionParserInterface, array<ExpressionParserInterface>>
*/
public function getPrecedenceChanges(): \WeakMap
{
if (null === $this->precedenceChanges) {
$this->precedenceChanges = new \WeakMap();
foreach ($this as $ep) {
if (!$ep->getPrecedenceChange()) {
continue;
}
$min = min($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence());
$max = max($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence());
foreach ($this as $e) {
if ($e->getPrecedence() > $min && $e->getPrecedence() < $max) {
if (!isset($this->precedenceChanges[$e])) {
$this->precedenceChanges[$e] = [];
}
$this->precedenceChanges[$e][] = $ep;
}
}
}
}
return $this->precedenceChanges;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\Unary\SpreadUnary;
use Twig\Node\Expression\Variable\ContextVariable;
use Twig\Node\Expression\Variable\LocalVariable;
use Twig\Node\Nodes;
use Twig\Parser;
use Twig\Token;
trait ArgumentsTrait
{
private function parseCallableArguments(Parser $parser, int $line, bool $parseOpenParenthesis = true): ArrayExpression
{
$arguments = new ArrayExpression([], $line);
foreach ($this->parseNamedArguments($parser, $parseOpenParenthesis) as $k => $n) {
$arguments->addElement($n, new LocalVariable($k, $line));
}
return $arguments;
}
private function parseNamedArguments(Parser $parser, bool $parseOpenParenthesis = true): Nodes
{
$args = [];
$stream = $parser->getStream();
if ($parseOpenParenthesis) {
$stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
}
$hasSpread = false;
while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
if ($args) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
// if the comma above was a trailing comma, early exit the argument parse loop
if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {
break;
}
}
$value = $parser->parseExpression();
if ($value instanceof SpreadUnary) {
$hasSpread = true;
} elseif ($hasSpread) {
throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
$name = null;
if (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) {
if (!$value instanceof ContextVariable) {
throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', $value::class), $token->getLine(), $stream->getSourceContext());
}
$name = $value->getAttribute('name');
$value = $parser->parseExpression();
}
if (null === $name) {
$args[] = $value;
} else {
$args[$name] = $value;
}
}
$stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
return new Nodes($args);
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrowFunctionExpression;
use Twig\Parser;
use Twig\Token;
/**
* @internal
*/
final class ArrowExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
{
// As the expression of the arrow function is independent from the current precedence, we want a precedence of 0
return new ArrowFunctionExpression($parser->parseExpression(), $expr, $token->getLine());
}
public function getName(): string
{
return '=>';
}
public function getDescription(): string
{
return 'Arrow function (x => expr)';
}
public function getPrecedence(): int
{
return 250;
}
public function getAssociativity(): InfixAssociativity
{
return InfixAssociativity::Left;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\ExpressionParser\PrecedenceChange;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\Binary\AbstractBinary;
use Twig\Parser;
use Twig\Token;
/**
* @internal
*/
class BinaryOperatorExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
public function __construct(
/** @var class-string<AbstractBinary> */
private string $nodeClass,
private string $name,
private int $precedence,
private InfixAssociativity $associativity = InfixAssociativity::Left,
private ?PrecedenceChange $precedenceChange = null,
private ?string $description = null,
private array $aliases = [],
) {
}
/**
* @return AbstractBinary
*/
public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression
{
$right = $parser->parseExpression(InfixAssociativity::Left === $this->getAssociativity() ? $this->getPrecedence() + 1 : $this->getPrecedence());
return new ($this->nodeClass)($left, $right, $token->getLine());
}
public function getAssociativity(): InfixAssociativity
{
return $this->associativity;
}
public function getName(): string
{
return $this->name;
}
public function getDescription(): string
{
return $this->description ?? '';
}
public function getPrecedence(): int
{
return $this->precedence;
}
public function getPrecedenceChange(): ?PrecedenceChange
{
return $this->precedenceChange;
}
public function getAliases(): array
{
return $this->aliases;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\Ternary\ConditionalTernary;
use Twig\Parser;
use Twig\Token;
/**
* @internal
*/
final class ConditionalTernaryExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression
{
$then = $parser->parseExpression($this->getPrecedence());
if ($parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {
// Ternary operator (expr ? expr2 : expr3)
$else = $parser->parseExpression($this->getPrecedence());
} else {
// Ternary without else (expr ? expr2)
$else = new ConstantExpression('', $token->getLine());
}
return new ConditionalTernary($left, $then, $else, $token->getLine());
}
public function getName(): string
{
return '?';
}
public function getDescription(): string
{
return 'Conditional operator (a ? b : c)';
}
public function getPrecedence(): int
{
return 0;
}
public function getAssociativity(): InfixAssociativity
{
return InfixAssociativity::Left;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\Error\SyntaxError;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\Lexer;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MacroReferenceExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\Variable\TemplateVariable;
use Twig\Parser;
use Twig\Template;
use Twig\Token;
/**
* @internal
*/
final class DotExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
use ArgumentsTrait;
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
{
$stream = $parser->getStream();
$token = $stream->getCurrent();
$lineno = $token->getLine();
$arguments = new ArrayExpression([], $lineno);
$type = Template::ANY_CALL;
if ($stream->nextIf(Token::OPERATOR_TYPE, '(')) {
$attribute = $parser->parseExpression();
$stream->expect(Token::PUNCTUATION_TYPE, ')');
} else {
$token = $stream->next();
if (
$token->test(Token::NAME_TYPE)
|| $token->test(Token::NUMBER_TYPE)
|| ($token->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue()))
) {
$attribute = new ConstantExpression($token->getValue(), $token->getLine());
} else {
throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), $token->toEnglish()), $token->getLine(), $stream->getSourceContext());
}
}
if ($stream->test(Token::OPERATOR_TYPE, '(')) {
$type = Template::METHOD_CALL;
$arguments = $this->parseCallableArguments($parser, $token->getLine());
}
if (
$expr instanceof NameExpression
&& (
null !== $parser->getImportedSymbol('template', $expr->getAttribute('name'))
|| '_self' === $expr->getAttribute('name') && $attribute instanceof ConstantExpression
)
) {
return new MacroReferenceExpression(new TemplateVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$attribute->getAttribute('value'), $arguments, $expr->getTemplateLine());
}
return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno);
}
public function getName(): string
{
return '.';
}
public function getDescription(): string
{
return 'Get an attribute on a variable';
}
public function getPrecedence(): int
{
return 512;
}
public function getAssociativity(): InfixAssociativity
{
return InfixAssociativity::Left;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\Attribute\FirstClassTwigCallableReady;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\ExpressionParser\PrecedenceChange;
use Twig\Node\EmptyNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Parser;
use Twig\Token;
/**
* @internal
*/
final class FilterExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
use ArgumentsTrait;
private $readyNodes = [];
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
{
$stream = $parser->getStream();
$token = $stream->expect(Token::NAME_TYPE);
$line = $token->getLine();
if (!$stream->test(Token::OPERATOR_TYPE, '(')) {
$arguments = new EmptyNode();
} else {
$arguments = $this->parseNamedArguments($parser);
}
$filter = $parser->getFilter($token->getValue(), $line);
$ready = true;
if (!isset($this->readyNodes[$class = $filter->getNodeClass()])) {
$this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);
}
if (!$ready = $this->readyNodes[$class]) {
trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFilter" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);
}
return new $class($expr, $ready ? $filter : new ConstantExpression($filter->getName(), $line), $arguments, $line);
}
public function getName(): string
{
return '|';
}
public function getDescription(): string
{
return 'Twig filter call';
}
public function getPrecedence(): int
{
return 512;
}
public function getPrecedenceChange(): ?PrecedenceChange
{
return new PrecedenceChange('twig/twig', '3.21', 300);
}
public function getAssociativity(): InfixAssociativity
{
return InfixAssociativity::Left;
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\Attribute\FirstClassTwigCallableReady;
use Twig\Error\SyntaxError;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\Node\EmptyNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\MacroReferenceExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Parser;
use Twig\Token;
/**
* @internal
*/
final class FunctionExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
use ArgumentsTrait;
private $readyNodes = [];
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
{
$line = $token->getLine();
if (!$expr instanceof NameExpression) {
throw new SyntaxError('Function name must be an identifier.', $line, $parser->getStream()->getSourceContext());
}
$name = $expr->getAttribute('name');
if (null !== $alias = $parser->getImportedSymbol('function', $name)) {
return new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], $this->parseCallableArguments($parser, $line, false), $line);
}
$args = $this->parseNamedArguments($parser, false);
$function = $parser->getFunction($name, $line);
if ($function->getParserCallable()) {
$fakeNode = new EmptyNode($line);
$fakeNode->setSourceContext($parser->getStream()->getSourceContext());
return ($function->getParserCallable())($parser, $fakeNode, $args, $line);
}
if (!isset($this->readyNodes[$class = $function->getNodeClass()])) {
$this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);
}
if (!$ready = $this->readyNodes[$class]) {
trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFunction" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);
}
return new $class($ready ? $function : $function->getName(), $args, $line);
}
public function getName(): string
{
return '(';
}
public function getDescription(): string
{
return 'Twig function call';
}
public function getPrecedence(): int
{
return 512;
}
public function getAssociativity(): InfixAssociativity
{
return InfixAssociativity::Left;
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\Attribute\FirstClassTwigCallableReady;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\MacroReferenceExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Nodes;
use Twig\Parser;
use Twig\Token;
use Twig\TwigTest;
/**
* @internal
*/
class IsExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
use ArgumentsTrait;
private $readyNodes = [];
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
{
$stream = $parser->getStream();
$test = $parser->getTest($token->getLine());
$arguments = null;
if ($stream->test(Token::OPERATOR_TYPE, '(')) {
$arguments = $this->parseNamedArguments($parser);
} elseif ($test->hasOneMandatoryArgument()) {
$arguments = new Nodes([0 => $parser->parseExpression($this->getPrecedence())]);
}
if ('defined' === $test->getName() && $expr instanceof NameExpression && null !== $alias = $parser->getImportedSymbol('function', $expr->getAttribute('name'))) {
$expr = new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], new ArrayExpression([], $expr->getTemplateLine()), $expr->getTemplateLine());
}
$ready = $test instanceof TwigTest;
if (!isset($this->readyNodes[$class = $test->getNodeClass()])) {
$this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);
}
if (!$ready = $this->readyNodes[$class]) {
trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigTest" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);
}
return new $class($expr, $ready ? $test : $test->getName(), $arguments, $stream->getCurrent()->getLine());
}
public function getPrecedence(): int
{
return 100;
}
public function getName(): string
{
return 'is';
}
public function getDescription(): string
{
return 'Twig tests';
}
public function getAssociativity(): InfixAssociativity
{
return InfixAssociativity::Left;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Parser;
use Twig\Token;
/**
* @internal
*/
final class IsNotExpressionParser extends IsExpressionParser
{
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
{
return new NotUnary(parent::parse($parser, $expr, $token), $token->getLine());
}
public function getName(): string
{
return 'is not';
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Nodes;
use Twig\Parser;
use Twig\Template;
use Twig\Token;
/**
* @internal
*/
final class SquareBracketExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
{
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
{
$stream = $parser->getStream();
$lineno = $token->getLine();
$arguments = new ArrayExpression([], $lineno);
// slice?
$slice = false;
if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
$attribute = new ConstantExpression(0, $token->getLine());
} else {
$attribute = $parser->parseExpression();
}
if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
}
if ($slice) {
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
$length = new ConstantExpression(null, $token->getLine());
} else {
$length = $parser->parseExpression();
}
$filter = $parser->getFilter('slice', $token->getLine());
$arguments = new Nodes([$attribute, $length]);
$filter = new ($filter->getNodeClass())($expr, $filter, $arguments, $token->getLine());
$stream->expect(Token::PUNCTUATION_TYPE, ']');
return $filter;
}
$stream->expect(Token::PUNCTUATION_TYPE, ']');
return new GetAttrExpression($expr, $attribute, $arguments, Template::ARRAY_CALL, $lineno);
}
public function getName(): string
{
return '[';
}
public function getDescription(): string
{
return 'Array access';
}
public function getPrecedence(): int
{
return 512;
}
public function getAssociativity(): InfixAssociativity
{
return InfixAssociativity::Left;
}
}

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